-
Notifications
You must be signed in to change notification settings - Fork 48
gen_udev_rules: add raw partition rule generator #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Dmitry Baryshkov (lumag)
merged 1 commit into
qualcomm-linux:main
from
wenwfu:udev-raw-partition-rules
Sep 15, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| # Skip filesystem probing for known Qualcomm raw GPT partitions. | ||
| # | ||
| # Match by GPT partition name instead of kernel disk name. Disk enumeration is | ||
| # not stable across all boards, and a disk can contain both raw firmware and | ||
| # mountable filesystem partitions. | ||
| # | ||
| # 60-persistent-storage.rules creates partition metadata links after the blkid | ||
| # import. Since this rule skips that import for raw partitions, emit those | ||
| # links here from kernel-provided PARTNAME/PARTUUID properties. | ||
| ACTION=="remove", GOTO="qcom_raw_noblkid_end" | ||
| SUBSYSTEM!="block", GOTO="qcom_raw_noblkid_end" | ||
| ENV{DEVTYPE}!="partition", GOTO="qcom_raw_noblkid_end" | ||
| ENV{PARTNAME}=="", GOTO="qcom_raw_links" | ||
|
|
||
| # Raw partition name patterns | ||
| @QCOM_RAW_PARTITION_RULES@ | ||
| GOTO="qcom_raw_noblkid_end" | ||
|
|
||
| LABEL="qcom_raw_noblkid" | ||
| # Supported since systemd v252. Older versions probe as usual. | ||
| ENV{UDEV_DISABLE_PERSISTENT_STORAGE_BLKID_FLAG}="1" | ||
|
lumag marked this conversation as resolved.
|
||
|
|
||
| LABEL="qcom_raw_links" | ||
| # persistent partition links | ||
| ENV{ID_PATH}!="?*", IMPORT{parent}="ID_PATH" | ||
| ENV{PARTUUID}=="?*", SYMLINK+="disk/by-partuuid/$env{PARTUUID}" | ||
| ENV{PARTNAME}=="?*", OPTIONS+="string_escape=replace", SYMLINK+="disk/by-partlabel/$env{PARTNAME}" | ||
| ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part/by-partnum/%n" | ||
| ENV{ID_PATH}=="?*", ENV{PARTUUID}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}-part/by-partuuid/$env{PARTUUID}" | ||
| ENV{ID_PATH}=="?*", ENV{PARTNAME}=="?*", OPTIONS+="string_escape=replace", SYMLINK+="disk/by-path/$env{ID_PATH}-part/by-partlabel/$env{PARTNAME}" | ||
|
|
||
| LABEL="qcom_raw_noblkid_end" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. | ||
| # SPDX-License-Identifier: BSD-3-Clause |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| # Partition names known to contain filesystems. Raw rules are generated for | ||
| # every other partition name found in the supplied in-tree layouts. | ||
| efi | ||
| logfs | ||
| persist | ||
| rootfs | ||
| userdata | ||
| vm-data |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| """Generate udev rules for Qualcomm raw partitions.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import re | ||
| import sys | ||
| from pathlib import Path | ||
|
|
||
| from qcom_ptool.loaders import load as load_spec | ||
|
|
||
| DATA_DIR = Path(__file__).with_name("data") | ||
| FILESYSTEM_NAMES_FILE = DATA_DIR / "filesystem-partition-names.list" | ||
| TEMPLATE_FILE = DATA_DIR / "55-qcom-raw-partitions-noblkid.rules.in" | ||
| RULES_PLACEHOLDER = "@QCOM_RAW_PARTITION_RULES@" | ||
| NAME_RE = re.compile(r"[A-Za-z0-9_.+-]+") | ||
| DEFAULT_LAYOUT_GLOB = "platforms/*/*/partitions.conf" | ||
|
|
||
|
|
||
| def load_filesystem_names() -> set[str]: | ||
| """Load names of partitions that may contain filesystems.""" | ||
| names: set[str] = set() | ||
| for line_number, line in enumerate( | ||
| FILESYSTEM_NAMES_FILE.read_text(encoding="utf-8").splitlines(), start=1 | ||
| ): | ||
| name = line.partition("#")[0].strip() | ||
| if not name: | ||
| continue | ||
| if NAME_RE.fullmatch(name) is None: | ||
| raise ValueError( | ||
| f"{FILESYSTEM_NAMES_FILE}:{line_number}: invalid name: {name}" | ||
| ) | ||
| if name in names: | ||
| raise ValueError( | ||
| f"{FILESYSTEM_NAMES_FILE}:{line_number}: duplicate name: {name}" | ||
| ) | ||
| names.add(name) | ||
|
|
||
| if not names: | ||
| raise ValueError(f"filesystem name list is empty: {FILESYSTEM_NAMES_FILE}") | ||
| return names | ||
|
|
||
|
|
||
| def load_partition_names(inputs: list[Path]) -> set[str]: | ||
| """Load and merge partition names from all supplied layouts.""" | ||
| names: set[str] = set() | ||
| for path in inputs: | ||
| spec = load_spec(str(path)) | ||
| for partitions in spec["partitions"].values(): | ||
| for partition in partitions: | ||
| name = partition["label"] | ||
| if name and name != "last_parti": | ||
| if NAME_RE.fullmatch(name) is None: | ||
| raise ValueError(f"{path}: invalid partition name: {name}") | ||
| names.add(name) | ||
| return names | ||
|
|
||
|
|
||
| def load_raw_partition_names(inputs: list[Path]) -> list[str]: | ||
| """Return known raw partition names from the supplied layouts.""" | ||
| return sorted(load_partition_names(inputs) - load_filesystem_names()) | ||
|
|
||
|
|
||
| def discover_inputs(inputs: list[Path] | None) -> list[Path]: | ||
| """Use explicit layouts or discover all layouts in the current repo.""" | ||
| if inputs: | ||
| return inputs | ||
|
|
||
| discovered = sorted(Path.cwd().glob(DEFAULT_LAYOUT_GLOB)) | ||
| if not discovered: | ||
| raise ValueError( | ||
| "no partition layouts found; run from the qcom-ptool repository " | ||
| "or provide one or more -i/--input paths" | ||
| ) | ||
| return discovered | ||
|
|
||
|
|
||
| def render_rules(names: list[str]) -> str: | ||
| """Render the udev rules template for the supplied partition names.""" | ||
| rules = "\n".join( | ||
| f'ENV{{PARTNAME}}=="{name}", GOTO="qcom_raw_noblkid"' | ||
| for name in names | ||
| ) | ||
| template = TEMPLATE_FILE.read_text(encoding="utf-8") | ||
| if template.count(RULES_PLACEHOLDER) != 1: | ||
| raise ValueError("rules template must contain exactly one placeholder") | ||
| return template.replace(RULES_PLACEHOLDER, rules) | ||
|
|
||
|
|
||
| def generate_rules(inputs: list[Path] | None = None) -> str: | ||
| """Render rules for raw partitions in all supplied layouts.""" | ||
| return render_rules(load_raw_partition_names(discover_inputs(inputs))) | ||
|
|
||
|
|
||
| def parse_args(argv: list[str] | None = None) -> argparse.Namespace: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument( | ||
| "-i", | ||
| "--input", | ||
| action="append", | ||
| type=Path, | ||
| help="partition layout to scan; may be repeated (defaults to platforms/*/*/partitions.conf)", | ||
| ) | ||
| parser.add_argument("-o", "--output", required=True, type=Path) | ||
| return parser.parse_args(argv) | ||
|
|
||
|
|
||
| def main(argv: list[str] | None = None) -> int: | ||
| args = parse_args(argv) | ||
| try: | ||
| content = generate_rules(args.input) | ||
| args.output.parent.mkdir(parents=True, exist_ok=True) | ||
| args.output.write_text(content, encoding="utf-8") | ||
| except (OSError, ValueError) as error: | ||
| print(f"error: {error}", file=sys.stderr) | ||
| return 2 | ||
|
|
||
| print(f"generated: {args.output}") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. | ||
| # SPDX-License-Identifier: BSD-3-Clause | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
|
|
||
| from qcom_ptool import gen_udev_rules | ||
|
|
||
|
|
||
| REPO_ROOT = Path(__file__).resolve().parents[2] | ||
| LAYOUT = REPO_ROOT / "platforms" / "qcs6490-rb3gen2" / "ufs" / "partitions.conf" | ||
|
|
||
|
|
||
| def test_generate_rules_uses_layout_names() -> None: | ||
| rules = gen_udev_rules.generate_rules([LAYOUT]) | ||
|
|
||
| assert 'ENV{PARTNAME}=="xbl_a"' in rules | ||
| assert 'ENV{PARTNAME}=="cdt"' in rules | ||
| assert "rootfs" not in rules | ||
| assert 'ENV{PARTNAME}=="not-in-layout"' not in rules | ||
|
|
||
|
|
||
| def test_generate_rules_merges_layouts_and_deduplicates( | ||
| tmp_path: Path, | ||
| ) -> None: | ||
| first = tmp_path / "first.conf" | ||
| second = tmp_path / "second.conf" | ||
| first.write_text( | ||
| "--disk --type=ufs --size=1\n" | ||
| "--partition --name=raw_a --size=1KB --type-guid=1\n" | ||
| "--partition --name=rootfs --size=1KB --type-guid=2\n", | ||
| encoding="utf-8", | ||
| ) | ||
| second.write_text( | ||
| "--disk --type=ufs --size=1\n" | ||
| "--partition --name=raw_a --size=1KB --type-guid=1\n" | ||
| "--partition --name=raw_b --size=1KB --type-guid=2\n", | ||
| encoding="utf-8", | ||
| ) | ||
| rules = gen_udev_rules.generate_rules([first, second]) | ||
|
|
||
| assert rules.count('ENV{PARTNAME}=="raw_a"') == 1 | ||
| assert 'ENV{PARTNAME}=="raw_b"' in rules | ||
| assert 'ENV{PARTNAME}=="rootfs"' not in rules | ||
|
|
||
|
|
||
| def test_generate_rules_preserves_persistent_links() -> None: | ||
| rules = gen_udev_rules.generate_rules([LAYOUT]) | ||
|
|
||
| assert 'ENV{PARTNAME}=="", GOTO="qcom_raw_links"' in rules | ||
| assert 'ENV{UDEV_DISABLE_PERSISTENT_STORAGE_BLKID_FLAG}="1"' in rules | ||
| assert "UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG" not in rules | ||
| assert 'SYMLINK+="disk/by-partuuid/$env{PARTUUID}"' in rules | ||
| assert 'SYMLINK+="disk/by-partlabel/$env{PARTNAME}"' in rules | ||
|
|
||
|
|
||
| def test_load_filesystem_names_rejects_unsafe_name( | ||
| tmp_path: Path, monkeypatch: pytest.MonkeyPatch | ||
| ) -> None: | ||
| policy = tmp_path / "names.list" | ||
| policy.write_text('rootfs\nrootfs", RUN+="/bin/true\n', encoding="utf-8") | ||
| monkeypatch.setattr(gen_udev_rules, "FILESYSTEM_NAMES_FILE", policy) | ||
|
|
||
| with pytest.raises(ValueError, match="invalid name") as error: | ||
| gen_udev_rules.load_filesystem_names() | ||
|
|
||
| assert f"{policy}:2:" in str(error.value) | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("content", "message"), | ||
| [ | ||
| ("xbl_a\nxbl_a\n", "duplicate name"), | ||
| ("# comments only\n", "filesystem name list is empty"), | ||
| ], | ||
| ) | ||
| def test_load_filesystem_names_rejects_invalid_policy( | ||
| tmp_path: Path, | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| content: str, | ||
| message: str, | ||
| ) -> None: | ||
| policy = tmp_path / "names.list" | ||
| policy.write_text(content, encoding="utf-8") | ||
| monkeypatch.setattr(gen_udev_rules, "FILESYSTEM_NAMES_FILE", policy) | ||
|
|
||
| with pytest.raises(ValueError, match=message): | ||
| gen_udev_rules.load_filesystem_names() | ||
|
|
||
|
|
||
| def test_main_writes_rules(tmp_path: Path) -> None: | ||
| output = tmp_path / "rules.d" / "55-qcom.rules" | ||
|
|
||
| assert gen_udev_rules.main(["-i", str(LAYOUT), "-o", str(output)]) == 0 | ||
| assert 'ENV{PARTNAME}=="cdt"' in output.read_text(encoding="utf-8") | ||
|
|
||
|
|
||
| def test_main_discovers_repo_layouts(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: | ||
| monkeypatch.chdir(REPO_ROOT) | ||
| output = tmp_path / "rules" | ||
|
|
||
| assert gen_udev_rules.main(["-o", str(output)]) == 0 | ||
| assert 'ENV{PARTNAME}=="xbl_a"' in output.read_text(encoding="utf-8") | ||
|
|
||
|
|
||
| def test_generate_rules_rejects_missing_default_layouts( | ||
| tmp_path: Path, monkeypatch: pytest.MonkeyPatch | ||
| ) -> None: | ||
| monkeypatch.chdir(tmp_path) | ||
|
|
||
| with pytest.raises(ValueError, match="no partition layouts found"): | ||
| gen_udev_rules.generate_rules() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.