Coverage for linuxpy/sysfs.py: 53%
97 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-18 17:55 +0200
« prev ^ index » next coverage.py v7.6.1, created at 2024-10-18 17:55 +0200
1#
2# This file is part of the linuxpy project
3#
4# Copyright (c) 2023 Tiago Coutinho
5# Distributed under the GPLv3 license. See LICENSE for more info.
7import enum
8import functools
9import os
10import pathlib
12from linuxpy.types import Callable
13from linuxpy.util import make_find
15from . import mounts
16from .magic import Magic
17from .statfs import get_fs_type
18from .types import Iterable, Optional
20MAGIC = Magic.SYSFS
22MOUNT_PATH = mounts.sysfs()
23DEVICE_PATH = MOUNT_PATH / "bus/usb/devices"
24CLASS_PATH = MOUNT_PATH / "class"
25THERMAL_PATH = CLASS_PATH / "thermal"
26LED_PATH = CLASS_PATH / "leds"
29class Mode(enum.Enum):
30 enabled = "enabled"
31 disabled = "disabled"
34def is_sysfs(path) -> bool:
35 return get_fs_type(path) == MAGIC
38def is_available() -> bool:
39 return is_sysfs(MOUNT_PATH)
42def iter_read_uevent(path: os.PathLike):
43 path = pathlib.Path(path)
44 with path.open() as f:
45 for line in f:
46 yield line.strip().split("=", 1)
49class Attr:
50 def __init__(self, filename: Optional[str] = None, decode: Callable = str):
51 self.filename = filename
52 self.decode = decode
54 def __set_name__(self, owner, name):
55 if self.filename is None:
56 self.filename = name
58 def _path(self, obj):
59 return obj.syspath / self.filename
61 def __get__(self, obj, objtype=None):
62 with self._path(obj).open() as f:
63 return self.decode(f.read().strip())
65 def __set__(self, obj, value):
66 with self._path(obj).open("w") as f:
67 f.write(str(value))
70Str = functools.partial(Attr, decode=str)
71Int = functools.partial(Attr, decode=int)
74class Device:
75 syspath: pathlib.Path
76 _subsystem: Optional[str] = None
77 _attrs: Optional[dict] = None
79 def __init__(self, syspath: os.PathLike):
80 self.syspath = pathlib.Path(syspath)
82 def __enter__(self):
83 return self
85 def __exit__(self, *_):
86 pass
88 @classmethod
89 def from_syspath(cls, syspath):
90 syspath = pathlib.Path(syspath)
91 if not syspath.exists():
92 raise ValueError("Unknown syspath")
93 syspath = syspath.resolve()
94 return cls(syspath)
96 @property
97 def subsystem(self):
98 if self._subsystem is None:
99 self._subsystem = (self.syspath / "subsystem").resolve().stem
100 return self._subsystem
102 @property
103 def attrs(self) -> dict[str, str]:
104 if self._attrs is None:
105 self._attrs = dict(iter_read_uevent(self.syspath / "uevent"))
106 return self._attrs
108 @property
109 def devnum(self) -> int:
110 return (int(self.attrs["MAJOR"]) << 8) + int(self.attrs["MINOR"])
112 @property
113 def devpath(self) -> pathlib.Path:
114 return self.syspath
116 @property
117 def devname(self) -> str:
118 return self.attrs.get("DEVNAME", "")
121def iter_device_paths() -> Iterable[pathlib.Path]:
122 char = MOUNT_PATH / "dev" / "char"
123 block = MOUNT_PATH / "dev" / "block"
124 yield from char.iterdir()
125 yield from block.iterdir()
128def iter_devices() -> Iterable[Device]:
129 return (Device.from_syspath(path) for path in iter_device_paths())
132find = make_find(iter_devices)
135def main():
136 devs = find(find_all=True)
137 devs = sorted(devs, key=lambda dev: dev.subsystem)
138 for dev in devs:
139 major_minor = f"{dev.attrs['MAJOR']:>3}:{dev.attrs['MINOR']:<3}"
140 print(f"{dev.devname:<20} {dev.subsystem:<16} {major_minor:<7} ({dev.devnum:>6}) {dev.syspath}")
143if __name__ == "__main__": 143 ↛ 144line 143 didn't jump to line 144 because the condition on line 143 was never true
144 main()