Coverage for src/debputy/builtin_manifest_rules.py: 86%

81 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-10-12 15:06 +0000

1import re 

2from typing import Tuple, Optional 

3from collections.abc import Iterable 

4 

5from debputy.architecture_support import DpkgArchitectureBuildProcessValuesTable 

6from debputy.exceptions import PureVirtualPathError, TestPathWithNonExistentFSPathError 

7from debputy.intermediate_manifest import PathType 

8from debputy.manifest_parser.base_types import SymbolicMode, OctalMode, FileSystemMode 

9from debputy.manifest_parser.util import AttributePath 

10from debputy.packages import BinaryPackage 

11from debputy.path_matcher import ( 

12 MATCH_ANYTHING, 

13 MatchRule, 

14 ExactFileSystemPath, 

15 DirectoryBasedMatch, 

16 MatchRuleType, 

17 BasenameGlobMatch, 

18) 

19from debputy.substitution import Substitution 

20from debputy.types import VP 

21from debputy.util import _normalize_path, resolve_perl_config 

22 

23# Imported from dh_fixperms 

24_PERMISSION_NORMALIZATION_SOURCE_DEFINITION = "permission normalization" 

25attribute_path = AttributePath.builtin_path()[ 

26 _PERMISSION_NORMALIZATION_SOURCE_DEFINITION 

27] 

28_STD_FILE_MODE = OctalMode(0o644) 

29_PATH_FILE_MODE = OctalMode(0o755) 

30_HAS_BIN_SHBANG_RE = re.compile(rb"^#!\s*/(?:usr/)?s?bin", re.ASCII) 

31 

32 

33class _UsrShareDocMatchRule(DirectoryBasedMatch): 

34 def __init__(self) -> None: 

35 super().__init__( 

36 MatchRuleType.ANYTHING_BENEATH_DIR, 

37 _normalize_path("usr/share/doc", with_prefix=True), 

38 path_type=PathType.FILE, 

39 ) 

40 

41 def finditer(self, fs_root: VP, *, ignore_paths=None) -> Iterable[VP]: 

42 doc_dir = fs_root.lookup(self._directory) 

43 if doc_dir is None: 

44 return 

45 for path_in_doc_dir in doc_dir.iterdir: 

46 if ignore_paths is not None and ignore_paths(path_in_doc_dir): 46 ↛ 47line 46 didn't jump to line 47 because the condition on line 46 was never true

47 continue 

48 if path_in_doc_dir.is_file: 48 ↛ 49line 48 didn't jump to line 49 because the condition on line 48 was never true

49 yield path_in_doc_dir 

50 for subpath in path_in_doc_dir.iterdir: 

51 if subpath.name == "examples" and subpath.is_dir: 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true

52 continue 

53 if ignore_paths is not None: 53 ↛ 60line 53 didn't jump to line 60 because the condition on line 53 was always true

54 yield from ( 

55 f 

56 for f in subpath.all_paths() 

57 if f.is_file and not ignore_paths(f) 

58 ) 

59 else: 

60 yield from (f for f in subpath.all_paths() if f.is_file) 

61 

62 def describe_match_short(self) -> str: 

63 return f"All files beneath {self._directory}/ except .../<pkg>/examples" 

64 

65 def describe_match_exact(self) -> str: 

66 return self.describe_match_short() 

67 

68 

69class _ShebangScriptFiles(MatchRule): 

70 def __init__(self) -> None: 

71 super().__init__(MatchRuleType.GENERIC_GLOB) 

72 

73 def finditer(self, fs_root: VP, *, ignore_paths=None) -> Iterable[VP]: 

74 for p in fs_root.all_paths(): 

75 if not p.is_file or (ignore_paths and ignore_paths(p)): 

76 continue 

77 try: 

78 with p.open(byte_io=True) as fd: 

79 c = fd.read(32) 

80 except (PureVirtualPathError, TestPathWithNonExistentFSPathError): 

81 continue 

82 if _HAS_BIN_SHBANG_RE.match(c): 

83 yield p 

84 

85 @property 

86 def path_type(self) -> PathType | None: 

87 return PathType.FILE 

88 

89 def _full_pattern(self) -> str: 

90 return "built-in - not a valid pattern" 

91 

92 def describe_match_short(self) -> str: 

93 return "All scripts with a absolute #!-line for /(s)bin or /usr/(s)bin" 

94 

95 def describe_match_exact(self) -> str: 

96 return self.describe_match_short() 

97 

98 

99USR_SHARE_DOC_MATCH_RULE = _UsrShareDocMatchRule() 

100SHEBANG_SCRIPTS = _ShebangScriptFiles() 

101del _UsrShareDocMatchRule 

102del _ShebangScriptFiles 

103 

104 

105def builtin_mode_normalization_rules( 

106 dpkg_architecture_variables: DpkgArchitectureBuildProcessValuesTable, 

107 dctrl_bin: BinaryPackage, 

108 substitution: Substitution, 

109) -> Iterable[tuple[MatchRule, FileSystemMode]]: 

110 yield from ( 

111 ( 

112 MatchRule.from_path_or_glob( 

113 x, 

114 _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, 

115 path_type=PathType.FILE, 

116 ), 

117 _STD_FILE_MODE, 

118 ) 

119 for x in ( 

120 "*.so.*", 

121 "*.so", 

122 "*.la", 

123 "*.a", 

124 "*.js", 

125 "*.css", 

126 "*.scss", 

127 "*.sass", 

128 "*.jpeg", 

129 "*.jpg", 

130 "*.png", 

131 "*.gif", 

132 "*.cmxs", 

133 "*.node", 

134 ) 

135 ) 

136 

137 yield from ( 

138 ( 

139 MatchRule.recursive_beneath_directory( 

140 x, 

141 _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, 

142 path_type=PathType.FILE, 

143 ), 

144 _STD_FILE_MODE, 

145 ) 

146 for x in ( 

147 "usr/share/man", 

148 "usr/include", 

149 "usr/share/applications", 

150 "usr/share/lintian/overrides", 

151 "usr/share/themes", 

152 ) 

153 ) 

154 

155 # The dh_fixperms tool recuses for these directories, but probably should not (see #1006927) 

156 yield from ( 

157 ( 

158 MatchRule.from_path_or_glob( 

159 f"{x}/*", 

160 _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, 

161 path_type=PathType.FILE, 

162 ), 

163 _PATH_FILE_MODE, 

164 ) 

165 for x in ( 

166 "usr/bin", 

167 "usr/bin/mh", 

168 "bin", 

169 "usr/sbin", 

170 "sbin", 

171 "usr/games", 

172 "usr/libexec", 

173 "etc/init.d", 

174 ) 

175 ) 

176 

177 yield ( 

178 # Strictly speaking, dh_fixperms does a recursive search but in practice, it does not matter. 

179 MatchRule.from_path_or_glob( 

180 "etc/sudoers.d/*", 

181 _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, 

182 path_type=PathType.FILE, 

183 ), 

184 OctalMode(0o440), 

185 ) 

186 

187 # The reportbug rule 

188 yield ( 

189 ExactFileSystemPath( 

190 substitution.substitute( 

191 _normalize_path("usr/share/bug/{{PACKAGE}}"), 

192 _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, 

193 ) 

194 ), 

195 OctalMode(0o755), 

196 ) 

197 

198 yield ( 

199 MatchRule.recursive_beneath_directory( 

200 "usr/share/bug/{{PACKAGE}}", 

201 _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, 

202 path_type=PathType.FILE, 

203 substitution=substitution, 

204 ), 

205 OctalMode(0o644), 

206 ) 

207 

208 yield ( 

209 ExactFileSystemPath( 

210 substitution.substitute( 

211 _normalize_path("usr/share/bug/{{PACKAGE}}/script"), 

212 _PERMISSION_NORMALIZATION_SOURCE_DEFINITION, 

213 ) 

214 ), 

215 OctalMode(0o755), 

216 ) 

217 

218 yield ( 

219 USR_SHARE_DOC_MATCH_RULE, 

220 OctalMode(0o0644), 

221 ) 

222 

223 perl_config_data = resolve_perl_config(dpkg_architecture_variables, dctrl_bin) 

224 

225 yield from ( 

226 ( 

227 BasenameGlobMatch( 

228 "*.pm", 

229 only_when_in_directory=_normalize_path(perl_dir), 

230 path_type=PathType.FILE, 

231 recursive_match=True, 

232 ), 

233 _STD_FILE_MODE, 

234 ) 

235 for perl_dir in (perl_config_data.vendorlib, perl_config_data.vendorarch) 

236 ) 

237 

238 yield ( 

239 BasenameGlobMatch( 

240 "*.ali", 

241 only_when_in_directory=_normalize_path("usr/lib"), 

242 path_type=PathType.FILE, 

243 recursive_match=True, 

244 ), 

245 OctalMode(0o444), 

246 ) 

247 

248 yield ( 

249 SHEBANG_SCRIPTS, 

250 _PATH_FILE_MODE, 

251 ) 

252 

253 yield ( 

254 MATCH_ANYTHING, 

255 SymbolicMode.parse_filesystem_mode( 

256 "go=rX,u+rw,a-s", 

257 attribute_path["**/*"], 

258 ), 

259 )