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
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
def _open_lvm(self): """Performs mount actions on a LVM. Scans for active volume groups from the loopback device, activates it and fills :attr:`volumes` with the logical volumes. :raises NoLoopbackAvailableError: when no loopback was available :raises IncorrectFilesystemError: when the volume is not a volume group """ os.environ['LVM_SUPPRESS_FD_WARNINGS'] = '1' # find free loopback device self._find_loopback() time.sleep(0.2) # Scan for new lvm volumes result = _util.check_output_(["lvm", "pvscan"]) for l in result.splitlines(): if self.loopback in l or (self.offset == 0 and self.get_raw_path() in l): for vg in re.findall(r'VG (\S+)', l): self.info['volume_group'] = vg if not self.info.get('volume_group'): logger.warning("Volume is not a volume group. (Searching for %s)", self.loopback) raise IncorrectFilesystemError() # Enable lvm volumes _util.check_call_(["lvm", "vgchange", "-a", "y", self.info['volume_group']], stdout=subprocess.PIPE)
def 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
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
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
def _find_loopback(self, use_loopback=True, var_name='loopback'): """Finds a free loopback device that can be used. The loopback is stored in :attr:`loopback`. If *use_loopback* is True, the loopback will also be used directly. :returns: the loopback address :raises NoLoopbackAvailableError: if no loopback could be found """ # noinspection PyBroadException try: loopback = _util.check_output_(['losetup', '-f']).strip() setattr(self, var_name, loopback) except Exception: logger.warning("No free loopback device found.", exc_info=True) raise NoLoopbackAvailableError() # noinspection PyBroadException if use_loopback: try: cmd = [ 'losetup', '-o', str(self.offset), '--sizelimit', str(self.size), loopback, self.get_raw_path() ] if not self.disk.read_write: cmd.insert(1, '-r') _util.check_call_(cmd, stdout=subprocess.PIPE) except Exception: logger.exception("Loopback device could not be mounted.") raise NoLoopbackAvailableError() return loopback
def _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
def mount(self, volume): """Performs mount actions on a LVM. Scans for active volume groups from the loopback device, activates it and fills :attr:`volumes` with the logical volumes. :raises NoLoopbackAvailableError: when no loopback was available :raises IncorrectFilesystemError: when the volume is not a volume group """ os.environ['LVM_SUPPRESS_FD_WARNINGS'] = '1' # find free loopback device volume._find_loopback() time.sleep(0.2) try: # Scan for new lvm volumes result = _util.check_output_(["lvm", "pvscan"]) for l in result.splitlines(): if volume.loopback in l or (volume.offset == 0 and volume.get_raw_path() in l): for vg in re.findall(r'VG (\S+)', l): volume.info['volume_group'] = vg if not volume.info.get('volume_group'): logger.warning("Volume is not a volume group. (Searching for %s)", volume.loopback) raise IncorrectFilesystemError() # Enable lvm volumes _util.check_call_(["lvm", "vgchange", "-a", "y", volume.info['volume_group']], stdout=subprocess.PIPE) except Exception: volume._free_loopback() raise volume.volumes.vstype = 'lvm' # fills it up. for _ in volume.volumes.detect_volumes('lvm'): pass
def 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
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
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
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
def find_lvm_volumes(self, force=False): """Performs post-mount actions on a LVM. Scans for active volume groups from the loopback device, activates it and fills :attr:`volumes` with the logical volumes. If *force* is true, the LVM detection is ran even when the LVM is not mounted on a loopback device. """ if not self.loopback and not force: return [] # Scan for new lvm volumes result = _util.check_output_(["lvm", "pvscan"]) for l in result.splitlines(): if self.loopback in l or (self.offset == 0 and self.get_raw_base_path() in l): for vg in re.findall(r'VG (\S+)', l): self.volume_group = vg if not self.volume_group: logger.warning("Volume is not a volume group.") return [] # Enable lvm volumes _util.check_call_(["vgchange", "-a", "y", self.volume_group], stdout=subprocess.PIPE) # Gather information about lvolumes, gathering their label, size and raw path result = _util.check_output_(["lvdisplay", self.volume_group]) for l in result.splitlines(): if "--- Logical volume ---" in l: self.volumes.append(Volume(disk=self.disk, stats=self.stats, fsforce=self.fsforce, fsfallback=self.fsfallback, fstypes=self.fstypes, pretty=self.pretty, mountdir=self.mountdir)) self.volumes[-1].index = "{0}.{1}".format(self.index, len(self.volumes) - 1) self.volumes[-1].fsdescription = 'Logical Volume' self.volumes[-1].flag = 'alloc' self.volumes[-1].parent = self if "LV Name" in l: self.volumes[-1].label = l.replace("LV Name", "").strip() if "LV Size" in l: self.volumes[-1].size = l.replace("LV Size", "").strip() if "LV Path" in l: self.volumes[-1].lv_path = l.replace("LV Path", "").strip() self.volumes[-1].offset = 0 logger.info("{0} volumes found".format(len(self.volumes))) return self.volumes
def add_to_raid(self): """Adds the disk to a central RAID volume. This function will first test whether it is actually a RAID volume by using :func:`is_raid` and, if so, will add the disk to the array via a loopback device. :return: whether the addition succeeded""" if not self.is_raid(): return False # find free loopback device # noinspection PyBroadException try: self.loopback = _util.check_output_(['losetup', '-f']).strip() except Exception: logger.warning("No free loopback device found for RAID", exc_info=True) return False # mount image as loopback cmd = ['losetup', '-o', str(self.offset), self.loopback, self.get_raw_path()] if not self.read_write: cmd.insert(1, '-r') try: _util.check_call_(cmd, stdout=subprocess.PIPE) except Exception: logger.exception("Failed mounting image to loopback") return False try: # use mdadm to mount the loopback to a md device # incremental and run as soon as available output = _util.check_output_(['mdadm', '-IR', self.loopback], stderr=subprocess.STDOUT) match = re.findall(r"attached to ([^ ,]+)", output) if match: self.md_device = os.path.realpath(match[0]) logger.info("Mounted RAID to {0}".format(self.md_device)) except Exception as e: logger.exception("Failed mounting RAID.") return False return True
def mount(self, volume): """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
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)
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)
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)
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
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
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
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
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
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
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
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)
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
def mount(self, volume): """Command that is an alternative to the :func:`mount` command that opens a LUKS container. The opened volume is added to the subvolume set of this volume. Requires the user to enter the key manually. TODO: add support for :attr:`keys` :return: the Volume contained in the LUKS container, or None on failure. :raises NoLoopbackAvailableError: when no free loopback could be found :raises IncorrectFilesystemError: when this is not a LUKS volume :raises SubsystemError: when the underlying command fails """ # Open a loopback device volume._find_loopback() # Check if this is a LUKS device # noinspection PyBroadException try: _util.check_call_(["cryptsetup", "isLuks", volume.loopback], stderr=subprocess.STDOUT) # ret = 0 if isLuks except Exception: logger.warning("Not a LUKS volume") # clean the loopback device, we want this method to be clean as possible # noinspection PyBroadException try: volume._free_loopback() except Exception: pass raise IncorrectFilesystemError() try: extra_args = [] key = None if volume.key: t, v = volume.key.split(':', 1) if t == 'p': # passphrase key = v elif t == 'f': # key-file extra_args = ['--key-file', v] elif t == 'm': # master-key-file extra_args = ['--master-key-file', v] else: logger.warning("No key material provided for %s", volume) except ValueError: logger.exception("Invalid key material provided (%s) for %s. Expecting [arg]:[value]", volume.key, volume) volume._free_loopback() raise ArgumentError() # Open the LUKS container volume._paths['luks'] = 'image_mounter_luks_' + str(random.randint(10000, 99999)) # noinspection PyBroadException try: cmd = ["cryptsetup", "luksOpen", volume.loopback, volume._paths['luks']] cmd.extend(extra_args) if not volume.disk.read_write: cmd.insert(1, '-r') if key is not None: logger.debug('$ {0}'.format(' '.join(cmd))) # for py 3.2+, we could have used input=, but that doesn't exist in py2.7. p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.communicate(key.encode("utf-8")) p.wait() retcode = p.poll() if retcode: raise KeyInvalidError() else: _util.check_call_(cmd) except ImageMounterError: del volume._paths['luks'] volume._free_loopback() raise except Exception as e: del volume._paths['luks'] volume._free_loopback() raise SubsystemError(e) size = None # noinspection PyBroadException try: result = _util.check_output_(["cryptsetup", "status", volume._paths['luks']]) for l in result.splitlines(): if "size:" in l and "key" not in l: size = int(l.replace("size:", "").replace("sectors", "").strip()) * volume.disk.block_size except Exception: pass container = volume.volumes._make_single_subvolume(flag='alloc', offset=0, size=size) container.info['fsdescription'] = 'LUKS Volume' return container
def mount(self): """Mounts the base image on a temporary location using the mount method stored in :attr:`method`. If mounting was successful, :attr:`mountpoint` is set to the temporary mountpoint. If :attr:`read_write` is enabled, a temporary read-write cache is also created and stored in :attr:`rwpath`. :return: whether the mounting was successful :rtype: bool """ if self.parser.casename: self.mountpoint = tempfile.mkdtemp(prefix='image_mounter_', suffix='_' + self.parser.casename) else: self.mountpoint = tempfile.mkdtemp(prefix='image_mounter_') if self.read_write: self.rwpath = tempfile.mkstemp(prefix="image_mounter_rw_cache_")[1] disk_type = self.get_disk_type() methods = self._get_mount_methods(disk_type) cmds = [] for method in methods: if method == 'avfs': # avfs does not participate in the fallback stuff, unfortunately self._mount_avfs() self.disk_mounter = method self.was_mounted = True self.is_mounted = True return elif method == 'dummy': os.rmdir(self.mountpoint) self.mountpoint = "" logger.debug("Raw path to dummy is {}".format( self.get_raw_path())) self.disk_mounter = method self.was_mounted = True self.is_mounted = True return elif method == 'xmount': cmds.append([ 'xmount', '--in', 'ewf' if disk_type == 'encase' else 'dd' ]) if self.read_write: cmds[-1].extend(['--rw', self.rwpath]) cmds[-1].extend( self.paths) # specify all paths, xmount needs this :( cmds[-1].append(self.mountpoint) elif method == 'affuse': cmds.extend([[ 'affuse', '-o', 'allow_other', self.paths[0], self.mountpoint ], ['affuse', self.paths[0], self.mountpoint]]) elif method == 'ewfmount': cmds.extend([[ 'ewfmount', '-X', 'allow_other', self.paths[0], self.mountpoint ], ['ewfmount', self.paths[0], self.mountpoint]]) elif method == 'vmware-mount': cmds.append([ 'vmware-mount', '-r', '-f', self.paths[0], self.mountpoint ]) elif method == 'nbd': _util.check_output_(['modprobe', 'nbd', 'max_part=63']) # Load nbd driver try: self._paths['nbd'] = _util.get_free_nbd_device( ) # Get free nbd device except NoNetworkBlockAvailableError: logger.warning("No free network block device found.", exc_info=True) raise cmds.extend([[ 'qemu-nbd', '--read-only', '-c', self._paths['nbd'], self.paths[0] ]]) else: raise ArgumentError("Unknown mount method {0}".format( self.disk_mounter)) for cmd in cmds: # noinspection PyBroadException try: _util.check_call_(cmd, stdout=subprocess.PIPE) # mounting does not seem to be instant, add a timer here time.sleep(.1) except Exception: logger.warning( 'Could not mount {0}, trying other method'.format( self.paths[0]), exc_info=True) continue else: raw_path = self.get_raw_path() logger.debug("Raw path to disk is {}".format(raw_path)) self.disk_mounter = cmd[0] if raw_path is None: raise MountpointEmptyError() self.was_mounted = True self.is_mounted = True return logger.error('Unable to mount {0}'.format(self.paths[0])) os.rmdir(self.mountpoint) self.mountpoint = "" raise MountError()
def 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])
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
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
def open_luks_container(self): """Command that is an alternative to the :func:`mount` command that opens a LUKS container. The opened volume is added to the subvolume set of this volume. Requires the user to enter the key manually. :return: the Volume contained in the LUKS container, or None on failure. """ # Open a loopback device if not self._find_loopback(): return None # Check if this is a LUKS device # noinspection PyBroadException try: _util.check_call_(["cryptsetup", "isLuks", self.loopback], stderr=subprocess.STDOUT) # ret = 0 if isLuks except Exception: logger.warning("Not a LUKS volume") # clean the loopback device, we want this method to be clean as possible # noinspection PyBroadException try: _util.check_call_(['losetup', '-d', self.loopback]) self.loopback = "" except Exception: pass return None # Open the LUKS container self.luks_path = 'image_mounter_' + str(random.randint(10000, 99999)) # noinspection PyBroadException try: cmd = ["cryptsetup", "luksOpen", self.loopback, self.luks_path] if not self.disk.read_write: cmd.insert(1, '-r') _util.check_call_(cmd) except Exception: self.luks_path = "" return None size = None # noinspection PyBroadException try: result = _util.check_output_(["cryptsetup", "status", self.luks_path]) for l in result.splitlines(): if "size:" in l and "key" not in l: size = int(l.replace("size:", "").replace("sectors", "").strip()) * self.disk.block_size except Exception: pass container = Volume(disk=self.disk, stats=self.stats, fsforce=self.fsforce, fsfallback=self.fsfallback, fstypes=self.fstypes, pretty=self.pretty, mountdir=self.mountdir) container.index = "{0}.0".format(self.index) container.fsdescription = 'LUKS container' container.flag = 'alloc' container.parent = self container.offset = 0 container.size = size self.volumes.append(container) return container
def unmount(self): """Unounts the volume from the filesystem. :raises SubsystemError: if one of the underlying processes fails :raises CleanupError: if the cleanup fails """ for volume in self.volumes: try: volume.unmount() except ImageMounterError: pass if self.is_mounted: logger.info("Unmounting volume %s", self) if self.loopback and self.info.get('volume_group'): _util.check_call_(["lvm", 'vgchange', '-a', 'n', self.info['volume_group']], wrap_error=True, stdout=subprocess.PIPE) self.info['volume_group'] = "" if self.loopback and self._paths.get('luks'): _util.check_call_(['cryptsetup', 'luksClose', self._paths['luks']], wrap_error=True, stdout=subprocess.PIPE) del self._paths['luks'] if self._paths.get('bde'): _util.clean_unmount(['fusermount', '-u'], self._paths['bde']) del self._paths['bde'] if self._paths.get('md'): md_path = self._paths['md'] del self._paths['md'] # removing it here to ensure we do not enter an infinite loop, will add it back later # MD arrays are a bit complicated, we also check all other volumes that are part of this array and # unmount them as well. logger.debug("All other volumes that use %s as well will also be unmounted", md_path) for v in self.disk.get_volumes(): if v != self and v._paths.get('md') == md_path: v.unmount() try: _util.check_output_(["mdadm", '--stop', md_path], stderr=subprocess.STDOUT) except Exception as e: self._paths['md'] = md_path raise SubsystemError(e) if self._paths.get('vss'): _util.clean_unmount(['fusermount', '-u'], self._paths['vss']) del self._paths['vss'] if self.loopback: _util.check_call_(['losetup', '-d', self.loopback], wrap_error=True) self.loopback = "" if self._paths.get('bindmounts'): for mp in self._paths['bindmounts']: _util.clean_unmount(['umount'], mp, rmdir=False) del self._paths['bindmounts'] if self.mountpoint: _util.clean_unmount(['umount'], self.mountpoint) self.mountpoint = "" if self._paths.get('carve'): try: shutil.rmtree(self._paths['carve']) except OSError as e: raise SubsystemError(e) else: del self._paths['carve'] self.is_mounted = False
def 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
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
def mount(self, volume): """Command that is an alternative to the :func:`mount` command that opens a LUKS container. The opened volume is added to the subvolume set of this volume. Requires the user to enter the key manually. TODO: add support for :attr:`keys` :return: the Volume contained in the LUKS container, or None on failure. :raises NoLoopbackAvailableError: when no free loopback could be found :raises IncorrectFilesystemError: when this is not a LUKS volume :raises SubsystemError: when the underlying command fails """ # Open a loopback device volume._find_loopback() # Check if this is a LUKS device # noinspection PyBroadException try: _util.check_call_(["cryptsetup", "isLuks", volume.loopback], stderr=subprocess.STDOUT) # ret = 0 if isLuks except Exception: logger.warning("Not a LUKS volume") # clean the loopback device, we want this method to be clean as possible # noinspection PyBroadException try: volume._free_loopback() except Exception: pass raise IncorrectFilesystemError() try: extra_args = [] key = None if volume.key: t, v = volume.key.split(':', 1) if t == 'p': # passphrase key = v elif t == 'f': # key-file extra_args = ['--key-file', v] elif t == 'm': # master-key-file extra_args = ['--master-key-file', v] else: logger.warning("No key material provided for %s", volume) except ValueError: logger.exception( "Invalid key material provided (%s) for %s. Expecting [arg]:[value]", volume.key, volume) volume._free_loopback() raise ArgumentError() # Open the LUKS container volume._paths['luks'] = 'image_mounter_luks_' + str( random.randint(10000, 99999)) # noinspection PyBroadException try: cmd = [ "cryptsetup", "luksOpen", volume.loopback, volume._paths['luks'] ] cmd.extend(extra_args) if not volume.disk.read_write: cmd.insert(1, '-r') if key is not None: logger.debug('$ {0}'.format(' '.join(cmd))) # for py 3.2+, we could have used input=, but that doesn't exist in py2.7. p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.communicate(key.encode("utf-8")) p.wait() retcode = p.poll() if retcode: raise KeyInvalidError() else: _util.check_call_(cmd) except ImageMounterError: del volume._paths['luks'] volume._free_loopback() raise except Exception as e: del volume._paths['luks'] volume._free_loopback() raise SubsystemError(e) size = None # noinspection PyBroadException try: result = _util.check_output_( ["cryptsetup", "status", volume._paths['luks']]) for l in result.splitlines(): if "size:" in l and "key" not in l: size = int( l.replace("size:", "").replace( "sectors", "").strip()) * volume.disk.block_size except Exception: pass container = volume.volumes._make_single_subvolume(flag='alloc', offset=0, size=size) container.info['fsdescription'] = 'LUKS Volume' return container
def mount(self): """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()