Example #1
0
    def _detect_single_volume(self):
        """'Detects' a single volume. It should not be called other than from a :class:`Disk`."""
        volume = self._make_single_subvolume(offset=0)
        is_directory = os.path.isdir(self.parent.get_raw_path())

        if is_directory:
            filesize = _util.check_output_(
                ['du', '-scDb', self.parent.get_raw_path()]).strip()
            if filesize:
                volume.size = int(filesize.splitlines()[-1].split()[0])

        else:
            description = _util.check_output_(
                ['file', '-sL', self.parent.get_raw_path()]).strip()
            if description:
                # description is the part after the :, until the first comma
                volume.info['fsdescription'] = description.split(
                    ': ', 1)[1].split(',', 1)[0].strip()
                if 'size' in description:
                    volume.size = int(
                        re.findall(r'size:? (\d+)', description)[0])
                else:
                    volume.size = os.path.getsize(self.parent.get_raw_path())

        volume.flag = 'alloc'
        self.volume_source = 'single'
        self._assign_disktype_data(volume)
        yield volume
Example #2
0
    def unmount_loopbacks(self):
        """Unmounts all loopback devices as identified by :func:`find_loopbacks`"""

        # re-index loopback devices
        self._index_loopbacks()

        for dev in self.find_loopbacks():
            _util.check_output_(['losetup', '-d', dev])
Example #3
0
    def unmount_loopbacks(self):
        """Unmounts all loopback devices as identified by :func:`find_loopbacks`"""

        # re-index loopback devices
        self._index_loopbacks()

        for dev in self.find_loopbacks():
            _util.check_output_(['losetup', '-d', dev])
    def load_disktype_data(self):
        """Calls the :command:`disktype` command and obtains the disk GUID from GPT volume systems. As we
        are running the tool anyway, the label is also extracted from the tool if it is not yet set.

        The disktype data is only loaded and not assigned to volumes yet.
        """

        if not _util.command_exists('disktype'):
            logger.warning("disktype not installed, could not detect volume type")
            return None

        disktype = _util.check_output_(['disktype', self.parent.get_raw_path()]).strip()

        current_partition = None
        for line in disktype.splitlines():
            if not line:
                continue
            # noinspection PyBroadException
            try:
                line = line.strip()

                find_partition_nr = re.match(r"^Partition (\d+):", line)
                if find_partition_nr:
                    current_partition = int(find_partition_nr.group(1))
                elif current_partition is not None:
                    if line.startswith("Type ") and "GUID" in line:
                        self._disktype[current_partition]['guid'] = \
                            line[line.index('GUID') + 5:-1].strip()  # output is between ()
                    elif line.startswith("Partition Name "):
                        self._disktype[current_partition]['label'] = \
                            line[line.index('Name ') + 6:-1].strip()  # output is between ""
            except Exception:
                logger.exception("Error while parsing disktype output")
                return
Example #5
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)
Example #6
0
    def find_volume_groups(self):
        """Finds all volume groups that are mounted through a loopback originating from :attr:`orig_re_pattern`.

        Generator yields tuples of vgname, pvname
        """

        os.environ['LVM_SUPPRESS_FD_WARNINGS'] = '1'

        # find volume groups
        try:
            result = _util.check_output_(['pvdisplay'])
            pvname = vgname = None
            for line in result.splitlines():
                if '--- Physical volume ---' in line:
                    pvname = vgname = None
                elif "PV Name" in line:
                    pvname = line.replace("PV Name", "").strip()
                elif "VG Name" in line:
                    vgname = line.replace("VG Name", "").strip()

                if pvname and vgname:
                    try:
                        # unmount volume groups with a physical volume originating from a disk image
                        if re.match(self.orig_re_pattern,
                                    self.loopbacks[pvname]):
                            yield vgname, pvname
                    except Exception:
                        pass
                    pvname = vgname = None

        except Exception:
            pass
Example #7
0
    def mount_directory(self):
        """Method that 'mounts' a directory. It actually just symlinks it. It is useful for AVFS mounts, that
        are not otherwise detected. This is a last resort method.
        """

        if not self.mount_directories:
            return

        volume = Volume(disk=self, **self.args)
        volume.offset = 0
        if self.index is None:
            volume.index = 0
        else:
            volume.index = '{0}.0'.format(self.index)

        filesize = _util.check_output_(['du', '-scDb', self.get_fs_path()]).strip()
        if filesize:
            volume.size = int(filesize.splitlines()[-1].split()[0])

        volume.flag = 'alloc'
        volume.fsdescription = 'Directory'
        self.volumes = [volume]
        self.volume_source = 'directory'

        for v in volume.init(no_stats=True):  # stats can't be retrieved from directory
            yield v
Example #8
0
    def _detect_vss_volumes(self, path):
        """Detect volume shadow copy volumes in the specified path."""

        try:
            volume_info = _util.check_output_([
                "vshadowinfo", "-o",
                str(self.parent.offset),
                self.parent.get_raw_path()
            ])
        except Exception as e:
            logger.exception(
                "Failed obtaining info from the volume shadow copies.")
            raise SubsystemError(e)

        current_store = None
        for line in volume_info.splitlines():
            line = line.strip()
            if line.startswith("Store:"):
                idx = line.split(":")[-1].strip()
                current_store = self._make_subvolume(
                    index=self._format_index(idx), flag='alloc', offset=0)
                current_store._paths['vss_store'] = os.path.join(
                    path, 'vss' + idx)
                current_store.info['fsdescription'] = 'VSS Store'
            elif line.startswith("Volume size"):
                current_store.size = int(
                    line.split(":")[-1].strip().split()[0])
            elif line.startswith("Creation time"):
                current_store.info['creation_time'] = line.split(
                    ":")[-1].strip()

        return self.volumes
Example #9
0
    def find_volume_groups(self):
        """Finds all volume groups that are mounted through a loopback originating from :attr:`orig_re_pattern`.

        Generator yields tuples of vgname, pvname
        """

        os.environ['LVM_SUPPRESS_FD_WARNINGS'] = '1'

        # find volume groups
        try:
            result = _util.check_output_(['pvdisplay'])
            pvname = vgname = None
            for line in result.splitlines():
                if '--- Physical volume ---' in line:
                    pvname = vgname = None
                elif "PV Name" in line:
                    pvname = line.replace("PV Name", "").strip()
                elif "VG Name" in line:
                    vgname = line.replace("VG Name", "").strip()

                if pvname and vgname:
                    try:
                        # unmount volume groups with a physical volume originating from a disk image
                        if re.match(self.orig_re_pattern, self.loopbacks[pvname]):
                            yield vgname, pvname
                    except Exception:
                        pass
                    pvname = vgname = None

        except Exception:
            pass
Example #10
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
Example #11
0
    def _detect_lvm_volumes(self, volume_group):
        """Gather information about lvolumes, gathering their label, size and raw path"""

        result = _util.check_output_(["lvm", "lvdisplay", volume_group])
        cur_v = None
        for l in result.splitlines():
            if "--- Logical volume ---" in l:
                cur_v = self._make_subvolume(index=self._format_index(
                    len(self)),
                                             flag='alloc')
                cur_v.info['fsdescription'] = 'Logical Volume'
            if "LV Name" in l:
                cur_v.info['label'] = l.replace("LV Name", "").strip()
            if "LV Size" in l:
                size, unit = l.replace("LV Size", "").strip().split(" ", 1)
                cur_v.size = int(
                    float(size.replace(',', '.')) * {
                        'KiB': 1024,
                        'MiB': 1024**2,
                        'GiB': 1024**3,
                        'TiB': 1024**4
                    }.get(unit, 1))
            if "LV Path" in l:
                cur_v._paths['lv'] = l.replace("LV Path", "").strip()
                cur_v.offset = 0

        logger.info("{0} volumes found".format(len(self)))
        self.volume_source = 'multi'
        return self.volumes
Example #12
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
Example #13
0
    def mount_single_volume(self):
        """Mounts a volume assuming that the mounted image does not contain a full disk image, but only a
        single volume.

        A new :class:`Volume` object is created based on the disk file and :func:`init` is called on this object.

        This function will typically yield one volume, although if the volume contains other volumes, multiple volumes
        may be returned.
        """

        volume = Volume(disk=self, **self.args)
        volume.offset = 0
        if self.index is None:
            volume.index = 0
        else:
            volume.index = '{0}.0'.format(self.index)

        description = _util.check_output_(['file', '-sL', self.get_fs_path()]).strip()
        if description:
            # description is the part after the :, until the first comma
            volume.fsdescription = description.split(': ', 1)[1].split(',', 1)[0].strip()
            if 'size' in description:
                volume.size = int(re.findall(r'size:? (\d+)', description)[0])
            else:
                volume.size = os.path.getsize(self.get_fs_path())

        volume.flag = 'alloc'
        self.volumes = [volume]
        self.volume_source = 'single'
        self._assign_disktype_data(volume)

        for v in volume.init(no_stats=True):  # stats can't  be retrieved from single volumes
            yield v
Example #14
0
    def detect(self, volume_system, vstype='detect'):
        """Gather information about lvolumes, gathering their label, size and raw path"""

        volume_group = volume_system.parent.info.get('volume_group')

        result = _util.check_output_(["lvm", "lvdisplay", volume_group])
        cur_v = None
        for l in result.splitlines():
            if "--- Logical volume ---" in l:
                cur_v = volume_system._make_subvolume(
                    index=self._format_index(volume_system, len(volume_system)),
                    flag='alloc'
                )
                cur_v.info['fsdescription'] = 'Logical Volume'
            if "LV Name" in l:
                cur_v.info['label'] = l.replace("LV Name", "").strip()
            if "LV Size" in l:
                size, unit = l.replace("LV Size", "").strip().split(" ", 1)
                cur_v.size = int(float(size.replace(',', '.')) * {'KiB': 1024, 'MiB': 1024 ** 2,
                                                                  'GiB': 1024 ** 3, 'TiB': 1024 ** 4}.get(unit, 1))
            if "LV Path" in l:
                cur_v._paths['lv'] = l.replace("LV Path", "").strip()
                cur_v.offset = 0

        logger.info("{0} volumes found".format(len(volume_system)))
        volume_system.volume_source = 'multi'
        return volume_system.volumes
Example #15
0
    def detect(self, volume_system, vstype='detect'):
        """Detect volume shadow copy volumes in the specified path."""

        path = volume_system.parent._paths['vss']

        try:
            volume_info = _util.check_output_(["vshadowinfo", "-o", str(volume_system.parent.offset),
                                               volume_system.parent.get_raw_path()])
        except Exception as e:
            logger.exception("Failed obtaining info from the volume shadow copies.")
            raise SubsystemError(e)

        current_store = None
        for line in volume_info.splitlines():
            line = line.strip()
            if line.startswith("Store:"):
                idx = line.split(":")[-1].strip()
                current_store = volume_system._make_subvolume(
                    index=self._format_index(volume_system, idx), flag='alloc', offset=0
                )
                current_store._paths['vss_store'] = os.path.join(path, 'vss' + idx)
                current_store.info['fsdescription'] = 'VSS Store'
            elif line.startswith("Volume size"):
                current_store.size = int(line.split(":")[-1].strip().split()[0])
            elif line.startswith("Creation time"):
                current_store.info['creation_time'] = line.split(":")[-1].strip()

        return volume_system.volumes
Example #16
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
 def modified_command(cmd, *args, **kwargs):
     if cmd[0] == 'parted':
         # A command that requests user input
         return check_output_([
             sys.executable, "-c",
             "exec(\"try: input('>> ')\\nexcept: pass\")"
         ], *args, **kwargs)
     return mock.DEFAULT
Example #18
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
Example #19
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
Example #20
0
    def mount(self, volume):
        """Add the volume to a RAID system. The RAID array is activated as soon as the array can be activated.

        :raises NoLoopbackAvailableError: if no loopback device was found
        """

        volume._find_loopback()

        raid_status = None
        try:
            # use mdadm to mount the loopback to a md device
            # incremental and run as soon as available
            output = _util.check_output_(['mdadm', '-IR', volume.loopback],
                                         stderr=subprocess.STDOUT)

            match = re.findall(r"attached to ([^ ,]+)", output)
            if match:
                volume._paths['md'] = os.path.realpath(match[0])
                if 'which is already active' in output:
                    logger.info(
                        "RAID is already active in other volume, using %s",
                        volume._paths['md'])
                    raid_status = 'active'
                elif 'not enough to start' in output:
                    volume._paths['md'] = volume._paths['md'].replace(
                        "/dev/md/", "/dev/md")
                    logger.info(
                        "RAID volume added, but not enough to start %s",
                        volume._paths['md'])
                    raid_status = 'waiting'
                else:
                    logger.info("RAID started at {0}".format(
                        volume._paths['md']))
                    raid_status = 'active'
        except Exception as e:
            logger.exception("Failed mounting RAID.")
            volume._free_loopback()
            raise SubsystemError(e)

        # search for the RAID volume
        for v in volume.disk.parser.get_volumes():
            if v._paths.get("md") == volume._paths['md'] and v.volumes:
                logger.debug("Adding existing volume %s to volume %s",
                             v.volumes[0], volume)
                v.volumes[0].info['raid_status'] = raid_status
                volume.volumes.volumes.append(v.volumes[0])
                return v.volumes[0]
        else:
            logger.debug("Creating RAID volume for %s", self)
            container = volume.volumes._make_single_subvolume(flag='alloc',
                                                              offset=0,
                                                              size=volume.size)
            container.info['fsdescription'] = 'RAID Volume'
            container.info['raid_status'] = raid_status
            return container
Example #21
0
    def unmount(self, allow_lazy=False):
        if self.mdpath is not None:
            # 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",
                self.mdpath)

            for v in self._iter_same_md_volumes():
                v.unmount(allow_lazy=allow_lazy)

            try:
                _util.check_output_(["mdadm", '--stop', self.mdpath],
                                    stderr=subprocess.STDOUT)
            except Exception as e:
                raise SubsystemError(e)

            self.mdpath = None

        super().unmount(allow_lazy=allow_lazy)
Example #22
0
    def _call_mount(self, volume, mountpoint, type=None, opts=""):
        """Calls the mount command, specifying the mount type and mount options."""

        # default arguments for calling mount
        if opts and not opts.endswith(','):
            opts += ","
        opts += 'loop,offset=' + str(volume.offset) + ',sizelimit=' + str(volume.size)

        # building the command
        cmd = ['mount', volume.get_raw_path(), mountpoint, '-o', opts]

        # add read-only if needed
        if not volume.disk.read_write:
            cmd[-1] += ',ro'

        # add the type if specified
        if type is not None:
            cmd += ['-t', type]

        _util.check_output_(cmd, stderr=subprocess.STDOUT)
Example #23
0
    def _call_mount(self, volume, mountpoint, type=None, opts=""):
        """Calls the mount command, specifying the mount type and mount options."""

        # default arguments for calling mount
        if opts and not opts.endswith(','):
            opts += ","
        opts += 'loop,offset=' + str(volume.offset) + ',sizelimit=' + str(
            volume.size)

        # building the command
        cmd = ['mount', volume.get_raw_path(), mountpoint, '-o', opts]

        # add read-only if needed
        if not volume.disk.read_write:
            cmd[-1] += ',ro'

        # add the type if specified
        if type is not None:
            cmd += ['-t', type]

        _util.check_output_(cmd, stderr=subprocess.STDOUT)
Example #24
0
    def _index_loopbacks(self):
        """Finds all loopbacks and stores them in :attr:`loopbacks`"""

        self.loopbacks = {}
        try:
            result = _util.check_output_(['losetup', '-a'])
            for line in result.splitlines():
                m = re.match(r'(.+): (.+) \((.+)\).*', line)
                if m:
                    self.loopbacks[m.group(1)] = m.group(3)
        except Exception:
            pass
Example #25
0
    def _index_loopbacks(self):
        """Finds all loopbacks and stores them in :attr:`loopbacks`"""

        self.loopbacks = {}
        try:
            result = _util.check_output_(['losetup', '-a'])
            for line in result.splitlines():
                m = re.match(r'(.+): (.+) \((.+)\).*', line)
                if m:
                    self.loopbacks[m.group(1)] = m.group(3)
        except Exception:
            pass
Example #26
0
    def _index_mountpoints(self):
        """Finds all mountpoints and stores them in :attr:`mountpoints`"""

        # find all mountponits
        self.mountpoints = {}
        # noinspection PyBroadException
        try:
            result = _util.check_output_(['mount'])
            for line in result.splitlines():
                m = re.match(r'(.+) on (.+) type (.+) \((.+)\)', line)
                if m:
                    self.mountpoints[m.group(2)] = (m.group(1), m.group(3), m.group(4))
        except Exception:
            pass
Example #27
0
    def _index_mountpoints(self):
        """Finds all mountpoints and stores them in :attr:`mountpoints`"""

        # find all mountponits
        self.mountpoints = {}
        # noinspection PyBroadException
        try:
            result = _util.check_output_(['mount'])
            for line in result.splitlines():
                m = re.match(r'(.+) on (.+) type (.+) \((.+)\)', line)
                if m:
                    self.mountpoints[m.group(2)] = (m.group(1), m.group(3), m.group(4))
        except Exception:
            pass
    def _detect_single_volume(self):
        """'Detects' a single volume. It should not be called other than from a :class:`Disk`."""
        volume = self._make_single_subvolume(offset=0)
        is_directory = os.path.isdir(self.parent.get_raw_path())

        if is_directory:
            filesize = _util.check_output_(['du', '-scDb', self.parent.get_raw_path()]).strip()
            if filesize:
                volume.size = int(filesize.splitlines()[-1].split()[0])

        else:
            description = _util.check_output_(['file', '-sL', self.parent.get_raw_path()]).strip()
            if description:
                # description is the part after the :, until the first comma
                volume.info['fsdescription'] = description.split(': ', 1)[1].split(',', 1)[0].strip()
                if 'size' in description:
                    volume.size = int(re.findall(r'size:? (\d+)', description)[0])
                else:
                    volume.size = os.path.getsize(self.parent.get_raw_path())

        volume.flag = 'alloc'
        self.volume_source = 'single'
        self._assign_disktype_data(volume)
        yield volume
Example #29
0
    def mount(self, volume):
        """Add the volume to a RAID system. The RAID array is activated as soon as the array can be activated.

        :raises NoLoopbackAvailableError: if no loopback device was found
        """

        volume._find_loopback()

        raid_status = None
        try:
            # use mdadm to mount the loopback to a md device
            # incremental and run as soon as available
            output = _util.check_output_(['mdadm', '-IR', volume.loopback], stderr=subprocess.STDOUT)

            match = re.findall(r"attached to ([^ ,]+)", output)
            if match:
                volume._paths['md'] = os.path.realpath(match[0])
                if 'which is already active' in output:
                    logger.info("RAID is already active in other volume, using %s", volume._paths['md'])
                    raid_status = 'active'
                elif 'not enough to start' in output:
                    volume._paths['md'] = volume._paths['md'].replace("/dev/md/", "/dev/md")
                    logger.info("RAID volume added, but not enough to start %s", volume._paths['md'])
                    raid_status = 'waiting'
                else:
                    logger.info("RAID started at {0}".format(volume._paths['md']))
                    raid_status = 'active'
        except Exception as e:
            logger.exception("Failed mounting RAID.")
            volume._free_loopback()
            raise SubsystemError(e)

        # search for the RAID volume
        for v in volume.disk.parser.get_volumes():
            if v._paths.get("md") == volume._paths['md'] and v.volumes:
                logger.debug("Adding existing volume %s to volume %s", v.volumes[0], volume)
                v.volumes[0].info['raid_status'] = raid_status
                volume.volumes.volumes.append(v.volumes[0])
                return v.volumes[0]
        else:
            logger.debug("Creating RAID volume for %s", self)
            container = volume.volumes._make_single_subvolume(flag='alloc', offset=0, size=volume.size)
            container.info['fsdescription'] = 'RAID Volume'
            container.info['raid_status'] = raid_status
            return container
Example #30
0
    def _get_blkid_type(self):
        """Retrieves the FS type from the blkid command."""
        try:
            result = _util.check_output_(['blkid', '-p', '-O', str(self.offset), self.get_raw_path()])
            if not result:
                return None

            # noinspection PyTypeChecker
            blkid_result = dict(re.findall(r'([A-Z]+)="(.+?)"', result))

            self.info['blkid_data'] = blkid_result

            if 'PTTYPE' in blkid_result and 'TYPE' not in blkid_result:
                return blkid_result.get('PTTYPE')
            else:
                return blkid_result.get('TYPE')

        except Exception:
            return None  # returning None is better here, since we do not care about the exception in determine_fs_type
Example #31
0
    def _get_blkid_type(self):
        """Retrieves the FS type from the blkid command."""
        try:
            result = _util.check_output_(['blkid', '-p', '-O', str(self.offset), self.get_raw_path()])
            if not result:
                return None

            # noinspection PyTypeChecker
            blkid_result = dict(re.findall(r'([A-Z]+)="(.+?)"', result))

            self.info['blkid_data'] = blkid_result

            if 'PTTYPE' in blkid_result and 'TYPE' not in blkid_result:
                return blkid_result.get('PTTYPE')
            else:
                return blkid_result.get('TYPE')

        except Exception:
            return None  # returning None is better here, since we do not care about the exception in determine_fs_type
Example #32
0
    def is_raid(self):
        """Tests whether this image (was) part of a RAID array. Requires :command:`mdadm` to be installed."""

        if not _util.command_exists('mdadm'):
            logger.info("mdadm not installed, could not detect RAID")
            return False

        # Scan for new lvm volumes
        # noinspection PyBroadException
        try:
            result = _util.check_output_(["mdadm", "--examine", self.get_raw_path()], stderr=subprocess.STDOUT)
            for l in result.splitlines():
                if 'Raid Level' in l:
                    logger.debug("Detected RAID level " + l[l.index(':') + 2:])
                    break
            else:
                return False
        except Exception:
            return False

        return True
Example #33
0
    def _load_disktype_data(self):
        """Calls the :command:`disktype` command and obtains the disk GUID from GPT volume systems. As we
        are running the tool anyway, the label is also extracted from the tool if it is not yet set.

        The disktype data is only loaded and not assigned to volumes yet.
        """

        if not _util.command_exists('disktype'):
            logger.warning(
                "disktype not installed, could not detect volume type")
            return None

        disktype = _util.check_output_(
            ['disktype', self.parent.get_raw_path()]).strip()

        current_partition = None
        for line in disktype.splitlines():
            if not line:
                continue
            # noinspection PyBroadException
            try:
                line = line.strip()

                find_partition_nr = re.match(r"^Partition (\d+):", line)
                if find_partition_nr:
                    current_partition = int(find_partition_nr.group(1))
                elif current_partition is not None:
                    if line.startswith("Type ") and "GUID" in line:
                        self._disktype[current_partition]['guid'] = \
                            line[line.index('GUID') + 5:-1].strip()  # output is between ()
                    elif line.startswith("Partition Name "):
                        self._disktype[current_partition]['label'] = \
                            line[line.index('Name ') + 6:-1].strip()  # output is between ""
            except Exception:
                logger.exception("Error while parsing disktype output")
                return
Example #34
0
    def _detect_parted_volumes(self, vstype='detect'):
        """Finds and mounts all volumes based on parted."""

        # for some reason, parted does not properly return extended volume types in its machine
        # output, so we need to execute it twice.
        meta_volumes = []
        # noinspection PyBroadException
        try:
            output = _util.check_output_(
                ['parted', self.parent.get_raw_path(), 'print'],
                stdin=subprocess.PIPE)
            for line in output.splitlines():
                if 'extended' in line:
                    meta_volumes.append(int(line.split()[0]))
        except Exception:
            logger.exception("Failed executing parted command.")
            # skip detection of meta volumes

        # noinspection PyBroadException
        try:
            # parted does not support passing in the vstype. It either works, or it doesn't.
            cmd = [
                'parted',
                self.parent.get_raw_path(), '-sm', 'unit s', 'print free'
            ]
            output = _util.check_output_(cmd, stdin=subprocess.PIPE)
            self.volume_source = 'multi'
        except Exception as e:
            logger.exception("Failed executing parted command")
            raise SubsystemError(e)

        num = 0
        for line in output.splitlines():
            if line.startswith(
                    "Warning"
            ) or not line or ':' not in line or line.startswith(
                    self.parent.get_raw_path()):
                continue
            line = line[:-1]  # remove last ;
            try:
                slot, start, end, length, description = line.split(':', 4)
                if ':' in description:
                    description, label, flags = description.split(':', 2)
                else:
                    description, label, flags = description, '', ''

                try:
                    slot = int(slot)
                except ValueError:
                    continue

                volume = self._make_subvolume(
                    index=self._format_index(num),
                    offset=int(start[:-1]) *
                    self.disk.block_size,  # remove last s
                    size=int(length[:-1]) * self.disk.block_size)
                volume.info['fsdescription'] = description
                if label:
                    volume.info['label'] = label
                if flags:
                    volume.info['parted_flags'] = flags

                # TODO: detection of meta volumes

                if description == 'free':
                    volume.flag = 'unalloc'
                    logger.info(
                        "Found unallocated space: block offset: {0}, length: {1}"
                        .format(start[:-1], length[:-1]))
                elif slot in meta_volumes:
                    volume.flag = 'meta'
                    volume.slot = slot
                    logger.info(
                        "Found meta volume: block offset: {0}, length: {1}".
                        format(start[:-1], length[:-1]))
                else:
                    volume.flag = 'alloc'
                    volume.slot = slot
                    self._assign_disktype_data(volume)
                    logger.info(
                        "Found allocated {2}: block offset: {0}, length: {1} ".
                        format(start[:-1], length[:-1],
                               volume.info['fsdescription']))
            except AttributeError:
                logger.exception("Error while parsing parted output")
                continue

            num += 1

            yield volume
Example #35
0
            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)
Example #36
0
 def modified_command(cmd, *args, **kwargs):
     if cmd[0] == 'parted':
         # A command that requests user input
         return check_output_(["python2", "-c", "exec \"try: raw_input('>> ')\\nexcept: pass\""],
                              *args, **kwargs)
     return mock.DEFAULT
Example #37
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
Example #38
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()
Example #39
0
    def unmount_volume_groups(self):
        """Unmounts all volume groups and related loopback devices as identified by :func:`find_volume_groups`"""

        for vgname, pvname in self.find_volume_groups():
            _util.check_output_(['lvchange', '-a', 'n', vgname])
            _util.check_output_(['losetup', '-d', pvname])
Example #40
0
    def _detect_mmls_volumes(self, vstype='detect'):
        """Finds and mounts all volumes based on mmls."""

        try:
            cmd = ['mmls']
            if self.parent.offset:
                cmd.extend(
                    ['-o',
                     str(self.parent.offset // self.disk.block_size)])
            if vstype != 'detect':
                cmd.extend(['-t', vstype])
            cmd.append(self.parent.get_raw_path())
            output = _util.check_output_(cmd, stderr=subprocess.STDOUT)
            self.volume_source = 'multi'
        except Exception as e:
            # some bug in sleuthkit makes detection sometimes difficult, so we hack around it:
            if hasattr(e, 'output') and "(GPT or DOS at 0)" in e.output.decode(
            ) and vstype != 'gpt':
                self.vstype = 'gpt'
                # noinspection PyBroadException
                try:
                    logger.warning(
                        "Error in retrieving volume info: mmls couldn't decide between GPT and DOS, "
                        "choosing GPT for you. Use --vstype=dos to force DOS.",
                        exc_info=True)
                    cmd = ['mmls', '-t', 'gpt', self.parent.get_raw_path()]
                    output = _util.check_output_(cmd, stderr=subprocess.STDOUT)
                    self.volume_source = 'multi'
                except Exception as e:
                    logger.exception("Failed executing mmls command")
                    raise SubsystemError(e)
            else:
                logger.exception("Failed executing mmls command")
                raise SubsystemError(e)

        output = output.split("Description", 1)[-1]
        for line in output.splitlines():
            if not line:
                continue
            # noinspection PyBroadException
            try:
                values = line.split(None, 5)

                # sometimes there are only 5 elements available
                description = ''
                index, slot, start, end, length = values[0:5]
                if len(values) > 5:
                    description = values[5]

                volume = self._make_subvolume(
                    index=self._format_index(int(index[:-1])),
                    offset=int(start) * self.disk.block_size,
                    size=int(length) * self.disk.block_size)
                volume.info['fsdescription'] = description
            except Exception:
                logger.exception("Error while parsing mmls output")
                continue

            if slot.lower() == 'meta':
                volume.flag = 'meta'
                logger.info(
                    "Found meta volume: block offset: {0}, length: {1}".format(
                        start, length))
            elif slot.lower().startswith('-----'):
                volume.flag = 'unalloc'
                logger.info(
                    "Found unallocated space: block offset: {0}, length: {1}".
                    format(start, length))
            else:
                volume.flag = 'alloc'
                if ":" in slot:
                    volume.slot = _util.determine_slot(*slot.split(':'))
                else:
                    volume.slot = _util.determine_slot(-1, slot)
                self._assign_disktype_data(volume)
                logger.info(
                    "Found allocated {2}: block offset: {0}, length: {1} ".
                    format(start, length, volume.info['fsdescription']))

            yield volume
Example #41
0
    def _mount_parted_volumes(self):
        """Finds and mounts all volumes based on parted."""

        # for some reason, parted does not properly return extended volume types in its machine
        # output, so we need to execute it twice.
        meta_volumes = []
        try:
            output = _util.check_output_(['parted', self.get_raw_path(), 'print'])
            for line in output.splitlines():
                if 'extended' in line:
                    meta_volumes.append(int(line.split()[0]))
        except Exception:
            logger.exception("Failed executing parted command.")
            # skip detection of meta volumes

        try:
            # parted does not support passing in the vstype. It either works, or it doesn't.
            cmd = ['parted', self.get_raw_path(), '-sm', 'unit s', 'print free']
            output = _util.check_output_(cmd)
            self.volume_source = 'multi'
        except Exception as e:
            logger.exception("Failed executing parted command")
            return

        num = 0
        for line in output.splitlines():
            if line.startswith("Warning") or not line or ':' not in line or line.startswith(self.get_raw_path()):
                continue
            line = line[:-1]  # remove last ;
            try:
                slot, start, end, length, description = line.split(':', 4)
                if ':' in description:
                    description, label, flags = description.split(':', 2)
                else:
                    description, label, flags = description, '', ''

                volume = Volume(disk=self, **self.args)
                self.volumes.append(volume)
                volume.offset = int(start[:-1]) * self.block_size  # remove last s
                volume.size = int(length[:-1]) * self.block_size
                volume.fsdescription = description
                if self.index is not None:
                    volume.index = '{0}.{1}'.format(self.index, num)
                else:
                    volume.index = num

                # TODO: detection of meta volumes

                if description == 'free':
                    volume.flag = 'unalloc'
                    logger.info("Found unallocated space: block offset: {0}, length: {1}".format(start[:-1], length[:-1]))
                elif int(slot) in meta_volumes:
                    volume.flag = 'meta'
                    volume.slot = int(slot)
                    logger.info("Found meta volume: block offset: {0}, length: {1}".format(start[:-1], length[:-1]))
                else:
                    volume.flag = 'alloc'
                    volume.slot = int(slot)
                    self._assign_disktype_data(volume)
                    logger.info("Found allocated {2}: block offset: {0}, length: {1} ".format(start[:-1], length[:-1],
                                                                                              volume.fsdescription))
            except AttributeError as e:
                logger.exception("Error while parsing parted output")
                continue

            num += 1

            # unalloc / meta partitions do not have stats and can not be mounted
            if volume.flag != 'alloc':
                yield volume
                continue

            for v in volume.init():
                yield v
    def _detect_parted_volumes(self, vstype='detect'):
        """Finds and mounts all volumes based on parted."""

        # for some reason, parted does not properly return extended volume types in its machine
        # output, so we need to execute it twice.
        meta_volumes = []
        # noinspection PyBroadException
        try:
            output = _util.check_output_(['parted', self.parent.get_raw_path(), 'print'])
            for line in output.splitlines():
                if 'extended' in line:
                    meta_volumes.append(int(line.split()[0]))
        except Exception:
            logger.exception("Failed executing parted command.")
            # skip detection of meta volumes

        # noinspection PyBroadException
        try:
            # parted does not support passing in the vstype. It either works, or it doesn't.
            cmd = ['parted', self.parent.get_raw_path(), '-sm', 'unit s', 'print free']
            output = _util.check_output_(cmd)
            self.volume_source = 'multi'
        except Exception as e:
            logger.exception("Failed executing parted command")
            raise SubsystemError(e)

        num = 0
        for line in output.splitlines():
            if line.startswith("Warning") or not line or ':' not in line or line.startswith(self.parent.get_raw_path()):
                continue
            line = line[:-1]  # remove last ;
            try:
                slot, start, end, length, description = line.split(':', 4)
                if ':' in description:
                    description, label, flags = description.split(':', 2)
                else:
                    description, label, flags = description, '', ''

                try:
                    slot = int(slot)
                except ValueError:
                    continue

                volume = self._make_subvolume(index=self._format_index(num),
                                              offset=int(start[:-1]) * self.disk.block_size,  # remove last s
                                              size=int(length[:-1]) * self.disk.block_size)
                volume.info['fsdescription'] = description

                # TODO: detection of meta volumes

                if description == 'free':
                    volume.flag = 'unalloc'
                    logger.info("Found unallocated space: block offset: {0}, length: {1}".format(start[:-1],
                                                                                                 length[:-1]))
                elif slot in meta_volumes:
                    volume.flag = 'meta'
                    volume.slot = slot
                    logger.info("Found meta volume: block offset: {0}, length: {1}".format(start[:-1], length[:-1]))
                else:
                    volume.flag = 'alloc'
                    volume.slot = slot
                    self._assign_disktype_data(volume)
                    logger.info("Found allocated {2}: block offset: {0}, length: {1} "
                                .format(start[:-1], length[:-1], volume.info['fsdescription']))
            except AttributeError:
                logger.exception("Error while parsing parted output")
                continue

            num += 1

            yield volume
Example #43
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
Example #44
0
            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)
Example #45
0
    def unmount_volume_groups(self):
        """Unmounts all volume groups and related loopback devices as identified by :func:`find_volume_groups`"""

        for vgname, pvname in self.find_volume_groups():
            _util.check_output_(['lvchange', '-a', 'n', vgname])
            _util.check_output_(['losetup', '-d', pvname])
Example #46
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
Example #47
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
    def _detect_mmls_volumes(self, vstype='detect'):
        """Finds and mounts all volumes based on mmls."""

        try:
            cmd = ['mmls']
            if self.parent.offset:
                cmd.extend(['-o', str(int(self.parent.offset) / self.disk.block_size)])
            if vstype != 'detect':
                cmd.extend(['-t', vstype])
            cmd.append(self.parent.get_raw_path())
            output = _util.check_output_(cmd, stderr=subprocess.STDOUT)
            self.volume_source = 'multi'
        except Exception as e:
            # some bug in sleuthkit makes detection sometimes difficult, so we hack around it:
            if hasattr(e, 'output') and "(GPT or DOS at 0)" in e.output.decode() and vstype != 'gpt':
                self.vstype = 'gpt'
                # noinspection PyBroadException
                try:
                    logger.warning("Error in retrieving volume info: mmls couldn't decide between GPT and DOS, "
                                   "choosing GPT for you. Use --vstype=dos to force DOS.", exc_info=True)
                    cmd = ['mmls', '-t', 'gpt', self.parent.get_raw_path()]
                    output = _util.check_output_(cmd, stderr=subprocess.STDOUT)
                    self.volume_source = 'multi'
                except Exception as e:
                    logger.exception("Failed executing mmls command")
                    raise SubsystemError(e)
            else:
                logger.exception("Failed executing mmls command")
                raise SubsystemError(e)

        output = output.split("Description", 1)[-1]
        for line in output.splitlines():
            if not line:
                continue
            # noinspection PyBroadException
            try:
                values = line.split(None, 5)

                # sometimes there are only 5 elements available
                description = ''
                index, slot, start, end, length = values[0:5]
                if len(values) > 5:
                    description = values[5]

                volume = self._make_subvolume(index=self._format_index(int(index[:-1])),
                                              offset=int(start) * self.disk.block_size,
                                              size=int(length) * self.disk.block_size)
                volume.info['fsdescription'] = description
            except Exception:
                logger.exception("Error while parsing mmls output")
                continue

            if slot.lower() == 'meta':
                volume.flag = 'meta'
                logger.info("Found meta volume: block offset: {0}, length: {1}".format(start, length))
            elif slot.lower().startswith('-----'):
                volume.flag = 'unalloc'
                logger.info("Found unallocated space: block offset: {0}, length: {1}".format(start, length))
            else:
                volume.flag = 'alloc'
                if ":" in slot:
                    volume.slot = _util.determine_slot(*slot.split(':'))
                else:
                    volume.slot = _util.determine_slot(-1, slot)
                self._assign_disktype_data(volume)
                logger.info("Found allocated {2}: block offset: {0}, length: {1} ".format(start, length,
                                                                                          volume.info['fsdescription']))

            yield volume
Example #49
0
    def _mount_mmls_volumes(self):
        """Finds and mounts all volumes based on mmls."""

        try:
            cmd = ['mmls']
            if self.vstype != 'detect':
                cmd.extend(['-t', self.vstype])
            cmd.append(self.get_raw_path())
            output = _util.check_output_(cmd, stderr=subprocess.STDOUT)
            self.volume_source = 'multi'
        except Exception as e:
            # some bug in sleuthkit makes detection sometimes difficult, so we hack around it:
            if hasattr(e, 'output') and "(GPT or DOS at 0)" in e.output.decode() and self.vstype != 'gpt':
                self.vstype = 'gpt'
                try:
                    logger.warning("Error in retrieving volume info: mmls couldn't decide between GPT and DOS, "
                                   "choosing GPT for you. Use --vstype=dos to force DOS.", exc_info=True)
                    cmd = ['mmls', '-t', self.vstype, self.get_raw_path()]
                    output = _util.check_output_(cmd, stderr=subprocess.STDOUT)
                    self.volume_source = 'multi'
                except Exception as e:
                    logger.exception("Failed executing mmls command")
                    return
            else:
                logger.exception("Failed executing mmls command")
                return

        output = output.split("Description", 1)[-1]
        for line in output.splitlines():
            if not line:
                continue
            try:
                values = line.split(None, 5)

                # sometimes there are only 5 elements available
                description = ''
                index, slot, start, end, length = values[0:5]
                if len(values) > 5:
                    description = values[5]

                volume = Volume(disk=self, **self.args)
                self.volumes.append(volume)

                volume.offset = int(start) * self.block_size
                volume.fsdescription = description
                if self.index is not None:
                    volume.index = '{0}.{1}'.format(self.index, int(index[:-1]))
                else:
                    volume.index = int(index[:-1])
                volume.size = int(length) * self.block_size
            except Exception as e:
                logger.exception("Error while parsing mmls output")
                continue

            if slot.lower() == 'meta':
                volume.flag = 'meta'
                logger.info("Found meta volume: block offset: {0}, length: {1}".format(start, length))
            elif slot.lower() == '-----':
                volume.flag = 'unalloc'
                logger.info("Found unallocated space: block offset: {0}, length: {1}".format(start, length))
            else:
                volume.flag = 'alloc'
                if ":" in slot:
                    volume.slot = _util.determine_slot(*slot.split(':'))
                else:
                    volume.slot = _util.determine_slot(-1, slot)
                self._assign_disktype_data(volume)
                logger.info("Found allocated {2}: block offset: {0}, length: {1} ".format(start, length,
                                                                                          volume.fsdescription))

            # unalloc / meta partitions do not have stats and can not be mounted
            if volume.flag != 'alloc':
                yield volume
                continue

            for v in volume.init():
                yield v
Example #50
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
Example #51
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()