Skip to content

Commit 80e748c

Browse files
a3fBastian-Krause
authored andcommitted
util: agents: add udisks2 agent for remote mounting
While udisksctl exists as a client tool to talk to the udisks2 daemon, it's not well-suited for programmatic use. We thus introduce an agent that is copied to the remote system and that uses PyGObject to directly talk DBus to the daemon to mount and unmount file systems. Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de> Signed-off-by: Bastian Krause <bst@pengutronix.de>
1 parent 1ac04df commit 80e748c

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

doc/configuration.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,14 @@ device.
610610
match:
611611
ID_PATH: 'pci-0000:06:00.0-usb-0:1.3.2:1.0-scsi-0:0:0:3'
612612
613+
Writing images to disk requires installation of ``dd`` or optionally
614+
``bmaptool`` on the same system as the block device.
615+
616+
For mounting the file system and writing into it,
617+
`PyGObject <https://pygobject.readthedocs.io/>`_ must be installed.
618+
For Debian, the necessary packages are `python3-gi` and `gir1.2-udisks-2.0`.
619+
This is not required for writing images to disks.
620+
613621
Arguments:
614622
- match (dict): key and value pairs for a udev match, see `udev Matching`_
615623

labgrid/util/agents/udisks2.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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

Comments
 (0)