Example #1
0
 def get_disks_xhci(self):
     """
     Compare
     1. the pci slot name of the devices using xhci
     2. the pci slot name of the disks,
        which is usb3 disks in this case so far,
     to make sure the usb3 disk does be on the controller using xhci
     """
     # LP: #1378724
     udev_client = GUdev.Client()
     # Get a collection of all udev devices corresponding to block devices
     udev_devices = get_udev_block_devices(udev_client)
     # Get a collection of all udev devices corresponding to xhci devices
     udev_devices_xhci = get_udev_xhci_devices(udev_client)
     if platform.machine() in ("aarch64", "armv7l"):
         enumerator = GUdev.Enumerator(client=udev_client)
         udev_devices_xhci = [
             device for device in enumerator.execute()
             if (device.get_driver() == 'xhci-hcd')
         ]
     for udev_device_xhci in udev_devices_xhci:
         pci_slot_name = udev_device_xhci.get_property('PCI_SLOT_NAME')
         xhci_devpath = udev_device_xhci.get_property('DEVPATH')
         for udev_device in udev_devices:
             devpath = udev_device.get_property('DEVPATH')
             if (self._compare_pci_slot_from_devpath(
                     devpath, pci_slot_name)):
                 self.rem_disks_xhci[udev_device.get_property(
                     'DEVNAME')] = 'xhci'
             if platform.machine() in ("aarch64", "armv7l"):
                 if xhci_devpath in devpath:
                     self.rem_disks_xhci[udev_device.get_property(
                         'DEVNAME')] = 'xhci'
     return self.rem_disks_xhci
Example #2
0
    def check(self, timeout):
        """
        Run the configured test and return the result

        The result is False if the test has failed.  The timeout, when
        non-zero, will make the test fail after the specified seconds have
        elapsed without conclusive result.
        """
        # Setup a timeout if requested
        if timeout > 0:
            GObject.timeout_add_seconds(timeout, self._on_timeout_expired)
        # Connect the observer to the bus. This will start giving us events
        # (actually when the loop starts later below)
        self._udisks2_observer.connect_to_bus(self._bus)
        # Get the reference snapshot of available devices
        self._reference_objects = copy.deepcopy(self._current_objects)
        self._dump_reference_udisks_objects()
        # Mark the current _reference_objects as ... reference, this is sadly
        # needed by _summarize_changes() as it sees the snapshot _after_ a
        # change has occurred and cannot determine if the slope of the 'edge'
        # of the change. It is purely needed for UI in verbose mode
        self._is_reference = True
        # A collection of objects that we gladly ignore because we already
        # reported on them being somehow inappropriate
        self._ignored_objects = set()
        # Get the reference snapshot of available udev devices
        self._reference_udev_devices = get_udev_block_devices(
            self._udev_client)
        self._dump_reference_udev_devices()
        # Start the loop and wait. The loop will exit either when:
        # 1) A proper device has been detected (either insertion or removal)
        # 2) A timeout (optional) has expired
        self._loop.run()
        # Return the outcome of the test
        return self._error
Example #3
0
    def _probe_disks_udisks2(self, bus):
        """
        Internal method used to probe / discover available disks using udisks2
        dbus interface using the provided dbus bus (presumably the system bus)
        """
        # We'll need udisks2 and udev to get the data we need
        udisks2_observer = UDisks2Observer()
        udisks2_model = UDisks2Model(udisks2_observer)
        udisks2_observer.connect_to_bus(bus)
        udev_client = GUdev.Client()
        # Get a collection of all udev devices corresponding to block devices
        udev_devices = get_udev_block_devices(udev_client)
        # Get a collection of all udisks2 objects
        udisks2_objects = udisks2_model.managed_objects

        # Let's get a helper to simplify the loop below

        def iter_filesystems_on_block_devices():
            """
            Generate a collection of UDisks2 object paths that
            have both the filesystem and block device interfaces
            """
            for udisks2_object_path, interfaces in udisks2_objects.items():
                if (UDISKS2_FILESYSTEM_INTERFACE in interfaces
                        and UDISKS2_BLOCK_INTERFACE in interfaces
                        and UDISKS2_LOOP_INTERFACE not in interfaces):
                    yield udisks2_object_path

        # We need to know about all IO candidates,
        # let's iterate over all the block devices reported by udisks2
        for udisks2_object_path in iter_filesystems_on_block_devices():
            # Get interfaces implemented by this object
            udisks2_object = udisks2_objects[udisks2_object_path]
            # Find the path of the udisks2 object that represents the drive
            # this object is a part of
            drive_object_path = (
                udisks2_object[UDISKS2_BLOCK_INTERFACE]['Drive'])
            # Lookup the drive object, if any. This can fail when
            try:
                drive_object = udisks2_objects[drive_object_path]
            except KeyError:
                logging.error("Unable to locate drive associated with %s",
                              udisks2_object_path)
                continue
            else:
                drive_props = drive_object[UDISKS2_DRIVE_INTERFACE]
            # Get the connection bus property from the drive interface of the
            # drive object. This is required to filter out the devices we don't
            # want to look at now.
            connection_bus = drive_props["ConnectionBus"]
            desired_connection_buses = set(
                [map_udisks1_connection_bus(device) for device in self.device])
            # Skip devices that are attached to undesired connection buses
            if connection_bus not in desired_connection_buses:
                continue
            # Lookup the udev object that corresponds to this object
            try:
                udev_device = lookup_udev_device(udisks2_object, udev_devices)
            except LookupError:
                logging.error(
                    "Unable to locate udev object that corresponds to: %s",
                    udisks2_object_path)
                continue
            # Get the block device pathname,
            # to avoid the confusion, this is something like /dev/sdbX
            dev_file = udev_device.get_device_file()
            parent = self._find_parent(dev_file.replace('/dev/', ''))
            if parent and find_pkname_is_root_mountpoint(parent, self.lsblk):
                continue
            # Skip the EFI System Partition
            part_type = udisks2_object[UDISKS2_PARTITION_INTERFACE]['Type']
            if part_type == ESP_GUID:
                continue
            # Get the list of mount points of this block device
            mount_points = (
                udisks2_object[UDISKS2_FILESYSTEM_INTERFACE]['MountPoints'])
            # Get the speed of the interconnect that is associated with the
            # block device we're looking at. This is purely informational but
            # it is a part of the required API
            interconnect_speed = get_interconnect_speed(udev_device)
            if interconnect_speed:
                self.rem_disks_speed[dev_file] = (interconnect_speed * 10**6)
            else:
                self.rem_disks_speed[dev_file] = None
            # Ensure it is a media card reader if this was explicitly requested
            drive_is_reader = is_memory_card(drive_props['Vendor'],
                                             drive_props['Model'],
                                             drive_props['Media'])
            if self.memorycard and not drive_is_reader:
                continue
            # The if/else test below simply distributes the mount_point to the
            # appropriate variable, to keep the API requirements. It is
            # confusing as _memory_cards is variable is somewhat dummy.
            if mount_points:
                # XXX: Arbitrarily pick the first of the mount points
                mount_point = mount_points[0]
                self.rem_disks_memory_cards[dev_file] = mount_point
                self.rem_disks[dev_file] = mount_point
            else:
                self.rem_disks_memory_cards_nm[dev_file] = None
                self.rem_disks_nm[dev_file] = None
Example #4
0
    def _probe_disks_udisks2_cli(self):
        # First we will build up a db of udisks info by scraping the output
        # of the dump command
        # TODO: remove the snap prefix when the alias becomes available
        proc = subprocess.Popen(['udisks2.udisksctl', 'dump'],
                                stdout=subprocess.PIPE)
        udisks_devices = {}
        current_bd = None
        current_interface = None
        while True:
            line = proc.stdout.readline().decode(sys.stdout.encoding)
            if line == '':
                break
            if line == '\n':
                current_bd = None
                current_interface = None
            if line.startswith('/org/freedesktop/UDisks2/'):
                path = line.strip()
                current_bd = os.path.basename(path).rstrip(':')
                udisks_devices[current_bd] = {}
                continue
            if current_bd is None:
                continue
            if line.startswith('  org.freedesktop'):
                current_interface = line.strip().rstrip(':')
                udisks_devices[current_bd][current_interface] = {}
                continue
            if current_interface is None:
                continue
            entry = ''.join(c for c in line if c not in '\n\t\' ')
            wanted_keys = (
                'Device:',
                'Drive:',
                'MountPoints:',
                'Vendor:',
                'ConnectionBus:',
                'Model:',
                'Media:',
                'Type:',
            )
            for key in wanted_keys:
                if entry.startswith(key):
                    udisks_devices[current_bd][current_interface][key] = (
                        entry[len(key):])

        # Now use the populated udisks structure to fill out the API used by
        # other _probe disks functions
        for device, interfaces in udisks_devices.items():
            # iterate over udisks objects that have both filesystem and
            # block device interfaces
            if (UDISKS2_FILESYSTEM_INTERFACE in interfaces
                    and UDISKS2_BLOCK_INTERFACE in interfaces):
                # To be an IO candidate there must be a drive object
                drive = interfaces[UDISKS2_BLOCK_INTERFACE].get('Drive:')
                if drive is None or drive == '/':
                    continue
                drive_object = udisks_devices[os.path.basename(drive)]

                # Get the connection bus property from the drive interface of
                # the drive object. This is required to filter out the devices
                # we don't want to look at now.
                connection_bus = (
                    drive_object[UDISKS2_DRIVE_INTERFACE]['ConnectionBus:'])
                desired_connection_buses = set([
                    map_udisks1_connection_bus(device)
                    for device in self.device
                ])
                # Skip devices that are attached to undesired connection buses
                if connection_bus not in desired_connection_buses:
                    continue

                dev_file = (interfaces[UDISKS2_BLOCK_INTERFACE].get('Device:'))

                parent = self._find_parent(dev_file.replace('/dev/', ''))
                if (parent and find_pkname_is_root_mountpoint(
                        parent, self.lsblk)):
                    continue

                # XXX: we actually only scrape the first one currently
                mount_point = (interfaces[UDISKS2_FILESYSTEM_INTERFACE].get(
                    'MountPoints:'))
                if mount_point == '':
                    mount_point = None

                # We need to skip-non memory cards if we look for memory cards
                # and vice-versa so let's inspect the drive and use heuristics
                # to detect memory cards (a memory card reader actually) now.
                if self.memorycard != is_memory_card(
                        drive_object[UDISKS2_DRIVE_INTERFACE]['Vendor:'],
                        drive_object[UDISKS2_DRIVE_INTERFACE]['Model:'],
                        drive_object[UDISKS2_DRIVE_INTERFACE]['Media:']):
                    continue

                # Skip the EFI System Partition
                part_type = drive_object[UDISKS2_PARTITION_INTERFACE]['Type:']
                if part_type == ESP_GUID:
                    continue

                if mount_point is None:
                    self.rem_disks_memory_cards_nm[dev_file] = None
                    self.rem_disks_nm[dev_file] = None
                else:
                    self.rem_disks_memory_cards[dev_file] = mount_point
                    self.rem_disks[dev_file] = mount_point

                # Get the speed of the interconnect that is associated with the
                # block device we're looking at. This is purely informational
                # but it is a part of the required API
                udev_devices = get_udev_block_devices(GUdev.Client())
                for udev_device in udev_devices:
                    if udev_device.get_device_file() == dev_file:
                        interconnect_speed = get_interconnect_speed(
                            udev_device)
                        if interconnect_speed:
                            self.rem_disks_speed[dev_file] = (
                                interconnect_speed * 10**6)
                        else:
                            self.rem_disks_speed[dev_file] = None
Example #5
0
    def _get_matching_devices(self, delta_records):
        """
        Internal method called that checks if the delta records match the type
        of device manipulation we were expecting. Only called from _on_change()

        Returns a set of paths of block devices that matched
        """
        # Results
        results = set()
        # Group changes by DBus object path
        grouped_records = collections.defaultdict(list)
        for record in delta_records:
            grouped_records[record.value.object_path].append(record)
        # Create another snapshot od udev devices so that we don't do it over
        # and over in the loop below (besides, if we did that then results
        # could differ each time).
        current_udev_devices = get_udev_block_devices(self._udev_client)
        # Iterate over all UDisks2 objects and their delta records
        for object_path, records_for_object in grouped_records.items():
            # Skip objects we already ignored and complained about before
            if object_path in self._ignored_objects:
                continue
            needs = set(('block-fs', 'partition', 'non-empty'))
            if not self._allow_unmounted:
                needs.add('mounted')

            # As a special exception when the ConnectionBus is allowed to be
            # empty, as is the case with eSATA devices, do not require the
            # filesystem to be mounted as gvfs may choose not to mount it
            # automatically.
            found = set()
            drive_object_path = None
            object_block_device = None
            for record in records_for_object:
                # Skip changes opposite to the ones we need
                if record.delta_dir != self._desired_delta_dir:
                    continue
                # For devices with empty "ConnectionBus" property, don't
                # require the device to be mounted
                if (
                    record.value.iface_name ==
                    "org.freedesktop.UDisks2.Drive" and
                    record.value.delta_type == DELTA_TYPE_PROP and
                    record.value.prop_name == "ConnectionBus" and
                    record.value.prop_value == ""
                ):
                    needs.remove('mounted')
                # Detect block devices designated for filesystems
                if (
                    record.value.iface_name ==
                    "org.freedesktop.UDisks2.Block" and
                    record.value.delta_type == DELTA_TYPE_PROP and
                    record.value.prop_name == "IdUsage" and
                    record.value.prop_value == "filesystem"
                ):
                    found.add('block-fs')
                # Memorize the block device path
                elif (
                    record.value.iface_name ==
                    "org.freedesktop.UDisks2.Block" and
                    record.value.delta_type == DELTA_TYPE_PROP and
                    record.value.prop_name == "PreferredDevice"
                ):
                    object_block_device = record.value.prop_value
                # Ensure the device is a partition
                elif (record.value.iface_name ==
                      "org.freedesktop.UDisks2.Partition" and
                      record.value.delta_type == DELTA_TYPE_IFACE):
                    found.add('partition')
                # Ensure the device is not empty
                elif (record.value.iface_name ==
                      "org.freedesktop.UDisks2.Block" and
                      record.value.delta_type == DELTA_TYPE_PROP and
                      record.value.prop_name == "Size" and
                      record.value.prop_value > 0):
                    found.add('non-empty')
                # Ensure the filesystem is mounted
                elif (record.value.iface_name ==
                      "org.freedesktop.UDisks2.Filesystem" and
                      record.value.delta_type == DELTA_TYPE_PROP and
                      record.value.prop_name == "MountPoints" and
                      record.value.prop_value != []):
                    found.add('mounted')
                    # On some systems partition are reported as mounted
                    # filesystems, without 'partition' record
                    if set(['partition', 'mounted']).issubset(needs):
                        needs.remove('partition')
                # Finally memorize the drive the block device belongs to
                elif (record.value.iface_name ==
                      "org.freedesktop.UDisks2.Block" and
                      record.value.delta_type == DELTA_TYPE_PROP and
                      record.value.prop_name == "Drive"):
                    drive_object_path = record.value.prop_value
            logging.debug("Finished analyzing %s, found: %s, needs: %s"
                          " drive_object_path: %s", object_path, found, needs,
                          drive_object_path)
            if not needs.issubset(found) or drive_object_path is None:
                continue
            # We've found our candidate, let's look at the drive it belongs
            # to. We need to do this as some properties are associated with
            # the drive, not the filesystem/block device and the drive may
            # not have been inserted at all.
            try:
                drive_object = self._current_objects[drive_object_path]
            except KeyError:
                # The drive may be removed along with the device, let's check
                # if we originally saw it
                try:
                    drive_object = self._reference_objects[drive_object_path]
                except KeyError:
                    logging.error(
                        "A block device belongs to a drive we could not find")
                    logging.error("missing drive: %r", drive_object_path)
                    continue
            try:
                drive_props = drive_object["org.freedesktop.UDisks2.Drive"]
            except KeyError:
                logging.error(
                    "A block device belongs to an object that is not a Drive")
                logging.error("strange object: %r", drive_object_path)
                continue
            # Ensure the drive is on the appropriate bus
            connection_bus = drive_props["ConnectionBus"]
            if connection_bus not in self._desired_connection_buses:
                logging.warning("The object %r belongs to drive %r that"
                                " is attached to the bus %r but but we are"
                                " looking for one of %r so it cannot match",
                                object_block_device, drive_object_path,
                                connection_bus,
                                ", ".join(self._desired_connection_buses))
                # Ignore this object so that we don't spam the user twice
                self._ignored_objects.add(object_path)
                continue
            # Ensure it is a media card reader if this was explicitly requested
            drive_is_reader = is_memory_card(
                drive_props['Vendor'], drive_props['Model'],
                drive_props['Media'])
            if self._desired_memory_card and not drive_is_reader:
                logging.warning(
                    "The object %s belongs to drive %s that does not seem to"
                    " be a media reader", object_block_device,
                    drive_object_path)
                # Ignore this object so that we don't spam the user twice
                self._ignored_objects.add(object_path)
                continue
            # Ensure the desired minimum speed is enforced
            if self._desired_minimum_speed:
                # We need to discover the speed of the UDisks2 object that is
                # about to be matched. Sadly UDisks2 no longer supports this
                # property so we need to poke deeper and resort to udev.
                #
                # The UDisks2 object that we are interested in implements a
                # number of interfaces, most notably
                # org.freedesktop.UDisks2.Block, that has the Device property
                # holding the unix filesystem path (like /dev/sdb1). We already
                # hold a reference to that as 'object_block_device'
                #
                # We take this as a start and attempt to locate the udev Device
                # (don't confuse with UDisks2.Device, they are _not_ the same)
                # that is associated with that path.
                if self._desired_delta_dir == DELTA_DIR_PLUS:
                    # If we are looking for additions then look at _current_
                    # collection of udev devices
                    udev_devices = current_udev_devices
                    udisks2_object = self._current_objects[object_path]
                else:
                    # If we are looking for removals then look at referece
                    # collection of udev devices
                    udev_devices = self._reference_udev_devices
                    udisks2_object = self._reference_objects[object_path]
                try:
                    # Try to locate the corresponding udev device among the
                    # collection we've selected. Use the drive object as the
                    # key -- this looks for the drive, not partition objects!
                    udev_device = lookup_udev_device(udisks2_object,
                                                     udev_devices)
                except LookupError:
                    logging.error("Unable to map UDisks2 object %s to udev",
                                  object_block_device)
                    # Ignore this object so that we don't spam the user twice
                    self._ignored_objects.add(object_path)
                    continue
                interconnect_speed = get_interconnect_speed(udev_device)
                # Now that we know the speed of the interconnect we can try to
                # validate it against our desired speed.
                if interconnect_speed is None:
                    logging.warning("Unable to determine interconnect speed of"
                                    " device %s", object_block_device)
                    # Ignore this object so that we don't spam the user twice
                    self._ignored_objects.add(object_path)
                    continue
                elif interconnect_speed < self._desired_minimum_speed:
                    logging.warning(
                        "Device %s is connected via an interconnect that has"
                        " the speed of %dMbit/s but the required speed was"
                        " %dMbit/s", object_block_device, interconnect_speed,
                        self._desired_minimum_speed)
                    # Ignore this object so that we don't spam the user twice
                    self._ignored_objects.add(object_path)
                    continue
                else:
                    logging.info("Device %s is connected via an USB"
                                 " interconnect with the speed of %dMbit/s",
                                 object_block_device, interconnect_speed)
            # Yay, success
            results.add(object_block_device)
        return results