|
| 1 | +""" |
| 2 | +This module implements mounting file systems via communication with udisksd. |
| 3 | +""" |
| 4 | +import logging |
| 5 | +import time |
| 6 | + |
| 7 | +import gi |
| 8 | +gi.require_version('UDisks', '2.0') |
| 9 | +from gi.repository import GLib, UDisks |
| 10 | + |
| 11 | +class UDisks2Device: |
| 12 | + UNMOUNT_MAX_RETRIES = 5 |
| 13 | + UNMOUNT_BUSY_WAIT = 3 # s |
| 14 | + |
| 15 | + def __init__(self, devpath): |
| 16 | + self._logger = logging.getLogger("Device: ") |
| 17 | + self.devpath = devpath |
| 18 | + client = UDisks.Client.new_sync(None) |
| 19 | + |
| 20 | + manager = client.get_object_manager() |
| 21 | + for obj in manager.get_objects(): |
| 22 | + block = obj.get_block() |
| 23 | + if not block: |
| 24 | + continue |
| 25 | + |
| 26 | + device_path = block.get_cached_property("Device").get_bytestring().decode('utf-8') |
| 27 | + if device_path == devpath: |
| 28 | + self.fs = obj.get_filesystem() |
| 29 | + if self.fs is None: |
| 30 | + raise ValueError(f"no filesystem found on {devpath}") |
| 31 | + |
| 32 | + return |
| 33 | + |
| 34 | + raise ValueError(f"No udisks2 device found for {devpath}") |
| 35 | + |
| 36 | + def mount(self, readonly=False): |
| 37 | + opts = GLib.Variant('a{sv}', {'options': GLib.Variant('s', 'ro' if readonly else 'rw')}) |
| 38 | + |
| 39 | + try: |
| 40 | + mountpoint = self.fs.call_mount_sync(opts, None) |
| 41 | + except GLib.GError as err: |
| 42 | + if not err.matches(UDisks.error_quark(), UDisks.Error.ALREADY_MOUNTED): |
| 43 | + raise err |
| 44 | + |
| 45 | + self._logger.warning('Unmounting lazily and remounting %s...', self.devpath) |
| 46 | + self._unmount_lazy() |
| 47 | + |
| 48 | + mountpoint = self.fs.call_mount_sync(opts, None) |
| 49 | + |
| 50 | + return mountpoint |
| 51 | + |
| 52 | + def _unmount_lazy(self): |
| 53 | + opts = GLib.Variant('a{sv}', {'force': GLib.Variant('b', True)}) |
| 54 | + |
| 55 | + try: |
| 56 | + self.fs.call_unmount_sync(opts, None) |
| 57 | + except GLib.GError as err: |
| 58 | + if not err.matches(UDisks.error_quark(), UDisks.Error.NOT_MOUNTED): |
| 59 | + raise err |
| 60 | + |
| 61 | + def _unmount(self): |
| 62 | + opts = GLib.Variant('a{sv}', {'force': GLib.Variant('b', False)}) |
| 63 | + |
| 64 | + for _ in range(self.UNMOUNT_MAX_RETRIES): |
| 65 | + try: |
| 66 | + self.fs.call_unmount_sync(opts, None) |
| 67 | + return |
| 68 | + except GLib.GError as err: |
| 69 | + if not err.matches(UDisks.error_quark(), UDisks.Error.DEVICE_BUSY): |
| 70 | + raise err |
| 71 | + |
| 72 | + self._logger.warning('waiting %s s for busy %s', |
| 73 | + self.UNMOUNT_BUSY_WAIT, self.devpath) |
| 74 | + time.sleep(self.UNMOUNT_BUSY_WAIT) |
| 75 | + |
| 76 | + raise TimeoutError("Timeout waiting for device to become non-busy") |
| 77 | + |
| 78 | + def unmount(self, lazy=False): |
| 79 | + if lazy: |
| 80 | + self._unmount_lazy() |
| 81 | + else: |
| 82 | + self._unmount() |
| 83 | + |
| 84 | +_devs = {} |
| 85 | + |
| 86 | +def _get_udisks2_dev(devpath): |
| 87 | + if devpath not in _devs: |
| 88 | + _devs[devpath] = UDisks2Device(devpath=devpath) |
| 89 | + return _devs[devpath] |
| 90 | + |
| 91 | +def handle_mount(devpath): |
| 92 | + dev = _get_udisks2_dev(devpath) |
| 93 | + return dev.mount() |
| 94 | + |
| 95 | +def handle_unmount(devpath, lazy=False): |
| 96 | + dev = _get_udisks2_dev(devpath) |
| 97 | + return dev.unmount(lazy=lazy) |
| 98 | + |
| 99 | +methods = { |
| 100 | + 'mount': handle_mount, |
| 101 | + 'unmount': handle_unmount, |
| 102 | +} |
0 commit comments