Repository: systemd/mkosi

Issue:
rpm compat symlink can result in circular links
##	Description

Building a Fedora 36 image using mkosi 13 on Fedora 35 results in circular symlinks between `/var/lib/rpm` and `/usr/lib/sysimage/rpm`.


##	Steps to Reproduce

On Fedora 35:

```
mkosi --distribution=fedora --release=36 --format=directory --output=rootfs --package=dnf

‣ Detaching namespace
‣ Setting up package cache…
‣ Setting up package cache ~/sandpit/.mkosi-fzlcqnin complete
‣ Setting up temporary workspace.
‣ Temporary workspace set up in ~/sandpit/.mkosi-ry8jf8u9
‣ Running second (final) stage…
‣  Mounting image…
‣  Setting up basic OS tree…
‣  Mounting Package Cache
‣  Installing Fedora Linux…
‣   Mounting API VFS
Fedora 36 - base ...
...
Complete!
‣   Unmounting API VFS
‣  Unmounting Package Cache
‣  Creating compat symlink /var/lib/rpm → /usr/lib/sysimage/rpm
‣  Recording packages in manifest…
error: Unable to open sqlite database ~/sandpit/.mkosi-ry8jf8u9/root/var/lib/rpm/rpmdb.sqlite: unable to open database file
error: cannot open Packages index using sqlite - Operation not permitted (1)
error: cannot open Packages database in ~/sandpit/.mkosi-ry8jf8u9/root/var/lib/rpm
‣  (Unmounting image)
Traceback (most recent call last):
  File "/usr/lib64/python3.10/runpy.py", line 196, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib64/python3.10/runpy.py", line 86, in _run_code
    exec(code, run_globals)
  File "/usr/lib/python3.10/site-packages/mkosi/__main__.py", line 36, in <module>
    main()
  File "/usr/lib/python3.10/site-packages/mkosi/__main__.py", line 27, in main
    run_verb(a)
  File "/usr/lib/python3.10/site-packages/mkosi/__init__.py", line 8413, in run_verb
    manifest = build_stuff(args)
  File "/usr/lib/python3.10/site-packages/mkosi/__init__.py", line 7776, in build_stuff
    image = build_image(args, root, manifest=manifest, do_run_build_script=False, cleanup=True)
  File "/usr/lib/python3.10/site-packages/mkosi/__init__.py", line 7525, in build_image
    manifest.record_packages(root)
  File "/usr/lib/python3.10/site-packages/mkosi/manifest.py", line 81, in record_packages
    self.record_rpm_packages(root)
  File "/usr/lib/python3.10/site-packages/mkosi/manifest.py", line 87, in record_rpm_packages
    c = run(
  File "/usr/lib/python3.10/site-packages/mkosi/backend.py", line 779, in run
    return subprocess.run(cmdline, check=check, stdout=stdout, stderr=stderr, **kwargs)
  File "/usr/lib64/python3.10/subprocess.py", line 524, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['rpm', '--root=~/sandpit/.mkosi-ry8jf8u9/root', '-qa', '--qf', '%{NEVRA}\\t%{SOURCERPM}\\t%{NAME}\\t%{ARCH}\\t%{SIZE}\\t%{INSTALLTIME}\\n']' returned non-zero exit status 1.
```

>	Note: I have replaced absolute paths with `~/sandpit`


##	Root Cause

Adding `os.system(f'chroot {root}')` to the `link_rpm_db` function in `mkosi/__init__.py` to see what's what before and after the symlinking:

```
mkosi --distribution=fedora --release=36 --format=directory --output=rootfs --package=dnf

‣ Detaching namespace
‣ Setting up package cache…
‣ Setting up package cache ~/sandpit/.mkosi-fzlcqnin complete
‣ Setting up temporary workspace.
‣ Temporary workspace set up in ~/sandpit/.mkosi-ry8jf8u9
‣ Running second (final) stage…
‣  Mounting image…
‣  Setting up basic OS tree…
‣  Mounting Package Cache
‣  Installing Fedora Linux…
‣   Mounting API VFS
Fedora 36 - base ...
...
Complete!
‣   Unmounting API VFS
‣  Unmounting Package Cache

bash-5.1# ls -al /var/lib/rpm
total 6760
drwxr-xr-x.  2 root root    4096 Aug 11 02:41 .
drwxr-xr-x. 14 root root    4096 Aug 11 02:41 ..
-rw-r--r--.  1 root root       0 Aug 11 02:41 .migratedb
-rw-r--r--.  1 root root       0 Aug 11 02:41 .rpm.lock
-rw-r--r--.  1 root root 6815744 Aug 11 02:41 rpmdb.sqlite
-rw-r--r--.  1 root root   98304 Aug 11 02:41 rpmdb.sqlite-shm
-rw-r--r--.  1 root root       0 Aug 11 02:41 rpmdb.sqlite-wal
bash-5.1# ls -al /usr/lib/sysimage/rpm/
total 8
drwxr-xr-x. 2 root root 4096 Aug  2 12:32 .
drwxr-xr-x. 3 root root 4096 Aug 11 02:41 ..
lrwxrwxrwx. 1 root root   33 Aug 11 02:41 .rpm.lock -> ../../../../var/lib/rpm/.rpm.lock
lrwxrwxrwx. 1 root root   36 Aug 11 02:41 rpmdb.sqlite -> ../../../../var/lib/rpm/rpmdb.sqlite
lrwxrwxrwx. 1 root root   40 Aug 11 02:41 rpmdb.sqlite-shm -> ../../../../var/lib/rpm/rpmdb.sqlite-shm
lrwxrwxrwx. 1 root root   40 Aug 11 02:41 rpmdb.sqlite-wal -> ../../../../var/lib/rpm/rpmdb.sqlite-wal
bash-5.1# exit

‣  Creating compat symlink /var/lib/rpm → /usr/lib/sysimage/rpm

bash-5.1# ls -al /var/lib/rpm
lrwxrwxrwx. 1 root root 26 Aug 11 02:43 /var/lib/rpm -> ../../usr/lib/sysimage/rpm
bash-5.1# ls -al /usr/lib/sysimage/rpm/
total 8
drwxr-xr-x. 2 root root 4096 Aug  2 12:32 .
drwxr-xr-x. 3 root root 4096 Aug 11 02:41 ..
lrwxrwxrwx. 1 root root   33 Aug 11 02:41 .rpm.lock -> ../../../../var/lib/rpm/.rpm.lock
lrwxrwxrwx. 1 root root   36 Aug 11 02:41 rpmdb.sqlite -> ../../../../var/lib/rpm/rpmdb.sqlite
lrwxrwxrwx. 1 root root   40 Aug 11 02:41 rpmdb.sqlite-shm -> ../../../../var/lib/rpm/rpmdb.sqlite-shm
lrwxrwxrwx. 1 root root   40 Aug 11 02:41 rpmdb.sqlite-wal -> ../../../../var/lib/rpm/rpmdb.sqlite-wal
bash-5.1# ls -al /var/lib/rpm/
total 8
drwxr-xr-x. 2 root root 4096 Aug  2 12:32 .
drwxr-xr-x. 3 root root 4096 Aug 11 02:41 ..
lrwxrwxrwx. 1 root root   33 Aug 11 02:41 .rpm.lock -> ../../../../var/lib/rpm/.rpm.lock
lrwxrwxrwx. 1 root root   36 Aug 11 02:41 rpmdb.sqlite -> ../../../../var/lib/rpm/rpmdb.sqlite
lrwxrwxrwx. 1 root root   40 Aug 11 02:41 rpmdb.sqlite-shm -> ../../../../var/lib/rpm/rpmdb.sqlite-shm
lrwxrwxrwx. 1 root root   40 Aug 11 02:41 rpmdb.sqlite-wal -> ../../../../var/lib/rpm/rpmdb.sqlite-wal
```

**There's the circular symlink:**
-	Files under `/usr/lib/sysimage/rpm/` are symlinks to `/var/lib/rpm`
-	`/var/lib/rpm` is replaced with a symlink to `/usr/lib/sysimage/rpm`
-	Now everything circularly links back to `/var/lib/rpm/*`


## Proposed Fix

Change `link_rpm_db` to first move `/var/lib/rpm` to `/usr/lib/sysimage/` like so:

```
diff --git a/mkosi/__init__.py b/mkosi/__init__.py
index 3f79095..24ee3c0 100644
--- a/mkosi/__init__.py
+++ b/mkosi/__init__.py
@@ -2066,7 +2066,8 @@ def link_rpm_db(root: Path) -> None:
     if rpmdb.exists() and not rpmdb_old.is_symlink():
         # We create the symlink in exactly the same fashion that Fedora do
         with complete_step("Creating compat symlink /var/lib/rpm → /usr/lib/sysimage/rpm"):
-            unlink_try_hard(rpmdb_old)
+            unlink_try_hard(rpmdb)
+            shutil.move(rpmdb_old, rpmdb)
             rpmdb_old.symlink_to("../../usr/lib/sysimage/rpm")
```



Discussion:
Please submit the patch as a PR

Resolving patch:
diff --git a/mkosi/__init__.py b/mkosi/__init__.py
--- a/mkosi/__init__.py
+++ b/mkosi/__init__.py
@@ -2006,9 +2006,13 @@ def link_rpm_db(root: Path) -> None:
     rpmdb = root / "usr/lib/sysimage/rpm"
     rpmdb_old = root / "var/lib/rpm"
     if rpmdb.exists() and not rpmdb_old.is_symlink():
-        # We create the symlink in exactly the same fashion that Fedora do
         with complete_step("Creating compat symlink /var/lib/rpm → /usr/lib/sysimage/rpm"):
-            unlink_try_hard(rpmdb_old)
+            # Move content, if any, from the old location to the new one
+            if rpmdb_old.exists():
+                unlink_try_hard(rpmdb)
+                shutil.move(cast(str, rpmdb_old), rpmdb)
+
+            # Create the symlink in exactly the same fashion that Fedora does
             rpmdb_old.symlink_to("../../usr/lib/sysimage/rpm")
 
 


Test patch: