summaryrefslogtreecommitdiff
path: root/app-admin/osforge/files/osforge-0.3.2-preserve-activation-owner.patch
blob: 6f2bc64dda3f44977ce7b0f7733f7e0f6f86e2cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
--- a/src/osforge/transition.py
+++ b/src/osforge/transition.py
@@ -47,12 +47,15 @@
         os.close(fd)
 
 
-def atomic_text(path: Path, text: str, *, exclusive: bool = False, mode: int = 0o644) -> None:
+def atomic_text(path: Path, text: str, *, exclusive: bool = False, mode: int = 0o644,
+                owner: tuple[int, int] | None = None) -> None:
     path.parent.mkdir(parents=True, exist_ok=True)
     fd, temporary = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
     temp = Path(temporary)
     try:
         with os.fdopen(fd, "w", encoding="utf-8") as handle:
+            if owner is not None:
+                os.fchown(handle.fileno(), *owner)
             os.fchmod(handle.fileno(), mode)
             handle.write(text)
             handle.flush()
--- a/src/osforge/repository_activation.py
+++ b/src/osforge/repository_activation.py
@@ -139,7 +139,9 @@
     if os.readlink(alias) != str(home / "current"):
         switch_pointer(alias, home / "current")
     if text != rendered:
-        atomic_text(config, rendered, mode=stat.S_IMODE(config.stat().st_mode))
+        info = config.stat()
+        atomic_text(config, rendered, mode=stat.S_IMODE(info.st_mode),
+                    owner=(info.st_uid, info.st_gid))
     return {"portage_config": str(config), "local_link": str(alias), "release": str(release)}
 
 
--- a/tests/test_repository_activation.py
+++ b/tests/test_repository_activation.py
@@ -66,6 +66,14 @@
         self.assertEqual(self.alias.resolve(), self.location())
         self.publish()  # Idempotent publication also checks the activation.
 
+    @unittest.skipUnless(os.geteuid() == 0, 'changing file owner requires root')
+    def test_registration_preserves_existing_owner_and_group(self):
+        os.chown(self.config, 65534, 65534)
+        candidate = self.update()
+        self.publish(candidate)
+        info = self.config.stat()
+        self.assertEqual((info.st_uid, info.st_gid), (65534, 65534))
+
     def test_interrupted_activation_reconciles_without_duplicate_commit(self):
         candidate = self.update()
         with patch('osforge.repository_activation.atomic_text', side_effect=OSError('disk failure')):
--- a/tests/test_merge.py
+++ b/tests/test_merge.py
@@ -147,7 +147,7 @@
     def test_merge_plan_writes_ledger_for_valid_configs(self) -> None:
         with TemporaryDirectory() as tmp:
             root = Path(tmp)
-            config = OsforgeConfig(path=root / "osforge.toml", state_root=root / "state")
+            config = OsforgeConfig(path=root / "osforge.toml", state_root=root / "state", merge_output_repo=root / "output")
 
             result = build_merge_plan(
                 config,