Coverage for linuxpy/sysfs.py: 71%
94 statements
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-13 08:09 +0200
« prev ^ index » next coverage.py v7.10.3, created at 2025-08-13 08:09 +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).resolve()
91 return cls(syspath)
93 @property
94 def subsystem(self):
95 if self._subsystem is None:
96 self._subsystem = (self.syspath / "subsystem").resolve().stem
97 return self._subsystem
99 @property
100 def attrs(self) -> dict[str, str]:
101 if self._attrs is None:
102 self._attrs = dict(iter_read_uevent(self.syspath / "uevent"))
103 return self._attrs
105 @property
106 def devnum(self) -> int:
107 return (int(self.attrs["MAJOR"]) << 8) + int(self.attrs["MINOR"])
109 @property
110 def devpath(self) -> pathlib.Path:
111 return self.syspath
113 @property
114 def devname(self) -> str:
115 return self.attrs.get("DEVNAME", "")
118def iter_device_paths() -> Iterable[pathlib.Path]:
119 char = MOUNT_PATH / "dev" / "char"
120 block = MOUNT_PATH / "dev" / "block"
121 yield from char.iterdir()
122 yield from block.iterdir()
125def iter_devices() -> Iterable[Device]:
126 return (Device.from_syspath(path) for path in iter_device_paths())
129find = make_find(iter_devices)
132def main():
133 devs = find(find_all=True)
134 devs = sorted(devs, key=lambda dev: dev.subsystem)
135 for dev in devs:
136 major_minor = f"{dev.attrs['MAJOR']:>3}:{dev.attrs['MINOR']:<3}"
137 print(f"{dev.devname:<20} {dev.subsystem:<16} {major_minor:<7} ({dev.devnum:>6}) {dev.syspath}")
140if __name__ == "__main__":
141 main()