Coverage for linuxpy/sysfs.py: 53%

97 statements  

« 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. 

6 

7import enum 

8import functools 

9import os 

10import pathlib 

11 

12from linuxpy.types import Callable 

13from linuxpy.util import make_find 

14 

15from . import mounts 

16from .magic import Magic 

17from .statfs import get_fs_type 

18from .types import Iterable, Optional 

19 

20MAGIC = Magic.SYSFS 

21 

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" 

27 

28 

29class Mode(enum.Enum): 

30 enabled = "enabled" 

31 disabled = "disabled" 

32 

33 

34def is_sysfs(path) -> bool: 

35 return get_fs_type(path) == MAGIC 

36 

37 

38def is_available() -> bool: 

39 return is_sysfs(MOUNT_PATH) 

40 

41 

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) 

47 

48 

49class Attr: 

50 def __init__(self, filename: Optional[str] = None, decode: Callable = str): 

51 self.filename = filename 

52 self.decode = decode 

53 

54 def __set_name__(self, owner, name): 

55 if self.filename is None: 

56 self.filename = name 

57 

58 def _path(self, obj): 

59 return obj.syspath / self.filename 

60 

61 def __get__(self, obj, objtype=None): 

62 with self._path(obj).open() as f: 

63 return self.decode(f.read().strip()) 

64 

65 def __set__(self, obj, value): 

66 with self._path(obj).open("w") as f: 

67 f.write(str(value)) 

68 

69 

70Str = functools.partial(Attr, decode=str) 

71Int = functools.partial(Attr, decode=int) 

72 

73 

74class Device: 

75 syspath: pathlib.Path 

76 _subsystem: Optional[str] = None 

77 _attrs: Optional[dict] = None 

78 

79 def __init__(self, syspath: os.PathLike): 

80 self.syspath = pathlib.Path(syspath) 

81 

82 def __enter__(self): 

83 return self 

84 

85 def __exit__(self, *_): 

86 pass 

87 

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) 

95 

96 @property 

97 def subsystem(self): 

98 if self._subsystem is None: 

99 self._subsystem = (self.syspath / "subsystem").resolve().stem 

100 return self._subsystem 

101 

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 

107 

108 @property 

109 def devnum(self) -> int: 

110 return (int(self.attrs["MAJOR"]) << 8) + int(self.attrs["MINOR"]) 

111 

112 @property 

113 def devpath(self) -> pathlib.Path: 

114 return self.syspath 

115 

116 @property 

117 def devname(self) -> str: 

118 return self.attrs.get("DEVNAME", "") 

119 

120 

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() 

126 

127 

128def iter_devices() -> Iterable[Device]: 

129 return (Device.from_syspath(path) for path in iter_device_paths()) 

130 

131 

132find = make_find(iter_devices) 

133 

134 

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}") 

141 

142 

143if __name__ == "__main__": 143 ↛ 144line 143 didn't jump to line 144 because the condition on line 143 was never true

144 main()