Skip to content

Commit 9eee442

Browse files
Storage management: Add SMART status to disk infos
- Also rewrite API using sdbus-python
1 parent b5afc8c commit 9eee442

File tree

5 files changed

+896
-42
lines changed

5 files changed

+896
-42
lines changed

debian/control

+2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ Depends: python3-all (>= 3.11),
1717
, python3-ldap, python3-zeroconf (>= 0.47), python3-lexicon,
1818
, python3-cryptography, python3-jwt, python3-passlib, python3-magic
1919
, python-is-python3, python3-pydantic, python3-email-validator
20+
, python3-sortedcollections
2021
, udisks2, udisks2-bcache, udisks2-btrfs, udisks2-lvm2, udisks2-zram
22+
, smartmontools
2123
, python3-zmq
2224
, nginx, nginx-extras (>=1.22)
2325
, apt, apt-transport-https, apt-utils, aptitude, dirmngr

src/disks.py

+72-40
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,77 @@
1+
import enum
2+
13
import dbus
4+
from sdbus import sd_bus_open_system, DbusInterfaceCommon
25
from yunohost.utils.system import binary_to_human
36

7+
__all__ = ["info", "do_list"]
48

5-
__all__ = ["info", "list"]
9+
from yunohost.utils.udisks2_interfaces import Udisks2Manager
610

711

812
UDISK_DRIVE_PATH = "/org/freedesktop/UDisks2/drives/"
913
UDISK_DRIVE_IFC = "org.freedesktop.UDisks2.Drive"
10-
11-
12-
def _query_udisks() -> list[tuple[str, dict]]:
13-
bus = dbus.SystemBus()
14-
manager = dbus.Interface(
15-
bus.get_object("org.freedesktop.UDisks2", "/org/freedesktop/UDisks2"),
16-
"org.freedesktop.DBus.ObjectManager",
17-
)
18-
19-
return sorted(
20-
(
21-
(name.removeprefix(UDISK_DRIVE_PATH), dev[UDISK_DRIVE_IFC])
22-
for name, dev in manager.GetManagedObjects().items()
23-
if name.startswith(UDISK_DRIVE_PATH)
24-
),
25-
key=lambda item: item[1]["SortKey"],
26-
)
14+
UDISK_DRIVE_ATA_IFC = "org.freedesktop.UDisks2.Drive.Ata"
15+
UDISK_DRIVE_NVME_IFC = "org.freedesktop.UDisks2.Manager.NVMe"
16+
17+
18+
class DiskState(enum.StrEnum):
19+
@staticmethod
20+
def _generate_next_value_(name, start, count, last_values):
21+
return name.upper()
22+
23+
TEST_SANE = enum.auto()
24+
TEST_IN_PROGRESS = enum.auto()
25+
TEST_FAILURE = enum.auto()
26+
"""Test result is unavailable for some reason"""
27+
TEST_UNKNOWN = enum.auto()
28+
"""No data. Either because not run or previously aborted"""
29+
UNKNOWN = enum.auto()
30+
31+
@staticmethod
32+
def parse(status: str | None):
33+
"""
34+
https://storaged.org/doc/udisks2-api/latest/gdbus-org.freedesktop.UDisks2.Drive.Ata.html#gdbus-property-org-freedesktop-UDisks2-Drive-Ata.SmartSelftestStatus
35+
https://storaged.org/doc/udisks2-api/latest/gdbus-org.freedesktop.UDisks2.NVMe.Controller.html#gdbus-property-org-freedesktop-UDisks2-NVMe-Controller.SmartSelftestStatus
36+
"""
37+
if not status:
38+
return DiskState.UNKNOWN
39+
40+
match status:
41+
case "success":
42+
return DiskState.TEST_SANE
43+
case "inprogress":
44+
return DiskState.TEST_IN_PROGRESS
45+
case (
46+
"fatal_error"
47+
| "known_seg_fail"
48+
| "unknown_seg_fail"
49+
| "fatal"
50+
| "error_unknown"
51+
| "error_electrical"
52+
| "error_servo"
53+
| "error_read"
54+
| "error_handling"
55+
):
56+
return DiskState.TEST_FAILURE
57+
case _:
58+
return DiskState.UNKNOWN
2759

2860

2961
def _disk_infos(name: str, drive: dict, human_readable=False):
3062
result = {
3163
"name": name,
32-
"model": drive["Model"],
33-
"serial": drive["Serial"],
34-
"removable": bool(drive["MediaRemovable"]),
35-
"size": binary_to_human(drive["Size"]) if human_readable else drive["Size"],
64+
"model": drive["model"],
65+
"serial": drive["serial"],
66+
"removable": bool(drive["media_removable"]),
67+
"size": binary_to_human(drive["size"]) if human_readable else drive["size"],
68+
"smartStatus": DiskState.parse(drive.get("smart_selftest_status")),
3669
}
3770

38-
if connection_bus := drive["ConnectionBus"]:
39-
result["connection_bus"] = connection_bus
71+
if "connection_bus" in drive:
72+
result["connectionBus"] = drive["connection_bus"]
4073

41-
if (rotation_rate := drive["RotationRate"]) == -1:
74+
if (rotation_rate := drive["rotation_rate"]) == -1:
4275
result.update(
4376
{
4477
"type": "HDD",
@@ -58,25 +91,24 @@ def _disk_infos(name: str, drive: dict, human_readable=False):
5891
return result
5992

6093

61-
def list(with_info=False, human_readable=False):
94+
def do_list(with_info=False, human_readable=False):
95+
bus = sd_bus_open_system()
96+
disks = Udisks2Manager(bus).get_disks()
6297
if not with_info:
63-
return [name for name, _ in _query_udisks()]
98+
return list(disks.keys())
6499

65-
result = []
66-
67-
for name, drive in _query_udisks():
68-
result.append(_disk_infos(name, drive, human_readable))
100+
result = [
101+
_disk_infos(name, disk.props, human_readable) for name, disk in disks.items()
102+
]
69103

70104
return {"disks": result}
71105

72106

73107
def info(name, human_readable=False):
74-
bus = dbus.SystemBus()
75-
drive = dbus.Interface(
76-
bus.get_object(
77-
"org.freedesktop.UDisks2", f"/org/freedesktop/UDisks2/drives/{name}"
78-
),
79-
dbus.PROPERTIES_IFACE,
80-
).GetAll("org.freedesktop.UDisks2.Drive")
81-
82-
return _disk_infos(name, drive, human_readable)
108+
bus = sd_bus_open_system()
109+
disk = Udisks2Manager(bus).get_disks().get(name)
110+
111+
if not disk:
112+
return f"Unknown disk with name {name}" if human_readable else None
113+
114+
return _disk_infos(name, disk.props, human_readable)

src/smart.py

Whitespace-only changes.

src/storage.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
def storage_disk_list(with_info=False, human_readable=False):
2-
from yunohost.disks import list
2+
from yunohost.disks import do_list
33

4-
return list(with_info=with_info, human_readable=human_readable)
4+
return do_list(with_info=with_info, human_readable=human_readable)
55

66

77
def storage_disk_info(name, human_readable=False):

0 commit comments

Comments
 (0)