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
« 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
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
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)
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 )
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)
62 def describe_match_short(self) -> str:
63 return f"All files beneath {self._directory}/ except .../<pkg>/examples"
65 def describe_match_exact(self) -> str:
66 return self.describe_match_short()
69class _ShebangScriptFiles(MatchRule):
70 def __init__(self) -> None:
71 super().__init__(MatchRuleType.GENERIC_GLOB)
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
85 @property
86 def path_type(self) -> PathType | None:
87 return PathType.FILE
89 def _full_pattern(self) -> str:
90 return "built-in - not a valid pattern"
92 def describe_match_short(self) -> str:
93 return "All scripts with a absolute #!-line for /(s)bin or /usr/(s)bin"
95 def describe_match_exact(self) -> str:
96 return self.describe_match_short()
99USR_SHARE_DOC_MATCH_RULE = _UsrShareDocMatchRule()
100SHEBANG_SCRIPTS = _ShebangScriptFiles()
101del _UsrShareDocMatchRule
102del _ShebangScriptFiles
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 )
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 )
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 )
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 )
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 )
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 )
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 )
218 yield (
219 USR_SHARE_DOC_MATCH_RULE,
220 OctalMode(0o0644),
221 )
223 perl_config_data = resolve_perl_config(dpkg_architecture_variables, dctrl_bin)
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 )
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 )
248 yield (
249 SHEBANG_SCRIPTS,
250 _PATH_FILE_MODE,
251 )
253 yield (
254 MATCH_ANYTHING,
255 SymbolicMode.parse_filesystem_mode(
256 "go=rX,u+rw,a-s",
257 attribute_path["**/*"],
258 ),
259 )