Repository: systemd/mkosi

Issue:
Issues with UsrOnly and scripts
I've attempted to build an image with the following command line without success:

```
$ sudo mkosi --bootable --format gpt_ext4 --output-dir $PWD --usr-only --workspace $PWD/workdir --release kinetic --prepare-script prepare-script.sh --build-script build-script.sh build
...
‣  Unmounting Package Cache
‣  Mounting Package Cache
‣  Running prepare script…
execv(/root/prepare) failed: No such file or directory
‣ Error: Workspace command /root/prepare build returned non-zero exit code 1.
‣  (Unmounting Package Cache)
‣  (Unmounting image)
‣  (Detaching /dev/loop2)
```

The issue appears to be a mismatch between `root_home()` (see [here](https://github.com/systemd/mkosi/blob/965e76fcd77c578adf5eca9513e1517300852230/mkosi/__init__.py#L1782-L1783)) and `run_prepare_script()` (see [here](https://github.com/systemd/mkosi/blob/965e76fcd77c578adf5eca9513e1517300852230/mkosi/__init__.py#L3502)).

I've tried:

  * Adjusting the path that `run_prepare_script()` uses for the script to compensate for UsrOnly. This resulted in almost the same error, just `/home-root/prepare` instead of `/root/prepare`.
  * Removing the special case on UsrOnly from `root_home()` which resulted in a recursion depth exceeded error when copying files.
  * Adding `--source-file-transfer mount` on top of the UsrOnly modification. For some reason I get an error while detaching the loop devices. The prepare script does run when doing this.

```
New situation:
Disklabel type: gpt
Disk identifier: 73046AFC-0CB0-F246-8F51-FAD7BC542DB6

Device        Start     End Sectors  Size Type
/dev/loop2p1     40  524327  524288  256M EFI System
/dev/loop2p2 524328 6815783 6291456    3G unknown

The partition table has been altered.
‣   Writing partition...
‣  Detaching /dev/loop2
Traceback (most recent call last):
  File "/usr/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/usr/local/lib/python3.8/dist-packages/mkosi-13-py3.8.egg/mkosi/__main__.py", line 36, in <module>
  File "/usr/local/lib/python3.8/dist-packages/mkosi-13-py3.8.egg/mkosi/__main__.py", line 27, in main
  File "/usr/local/lib/python3.8/dist-packages/mkosi-13-py3.8.egg/mkosi/__init__.py", line 8421, in run_verb
  File "/usr/local/lib/python3.8/dist-packages/mkosi-13-py3.8.egg/mkosi/__init__.py", line 7776, in build_stuff
  File "/usr/local/lib/python3.8/dist-packages/mkosi-13-py3.8.egg/mkosi/__init__.py", line 7586, in build_image
  File "/usr/lib/python3.8/contextlib.py", line 120, in __exit__
    next(self.gen)
  File "/usr/local/lib/python3.8/dist-packages/mkosi-13-py3.8.egg/mkosi/__init__.py", line 1019, in attach_image_loopback
  File "/usr/local/lib/python3.8/dist-packages/mkosi-13-py3.8.egg/mkosi/__init__.py", line 983, in ioctl_partition_remove
OSError: [Errno 16] Device or resource busy
```
I added some debugging and determined that detaching `/dev/loop2p2` is the issue which is not surprising. I don't know why it's still in use though.

I'm using the current commit (965e76fcd77c578adf5eca9513e1517300852230) and my directory tree looks like the following:
```
$ find
.
./workdir
./prepare-script.sh
./build-script.sh
```


Discussion:
I tried adding a bind mount as part of running the prepare script similar to [here](https://github.com/systemd/mkosi/blob/965e76fcd77c578adf5eca9513e1517300852230/mkosi/__init__.py#L7678-L7679). I still had the same `Device or resource busy` error when detaching the loop devices but the prepare script did run. I had to enable `--source-file-transfer mount` otherwise my workdir would've been copied in.

Resolving patch:
diff --git a/mkosi/__init__.py b/mkosi/__init__.py
--- a/mkosi/__init__.py
+++ b/mkosi/__init__.py
@@ -3947,6 +3947,15 @@ def insert_partition(
     with complete_step(f"Inserting partition of {format_bytes(part_size)}{ss}..."):
         args.partition_table.run_sfdisk(loopdev)
 
+        # Required otherwise the partition removal will fail
+        with open(loopdev, 'rb+') as f:
+            ioctl_partition_add(
+                f.fileno(),
+                part.number,
+                args.partition_table.partition_offset(part),
+                args.partition_table.partition_size(part)
+            )
+
     with complete_step("Writing partition..."):
         if ident == PartitionIdentifier.root:
             luks_format_root(args, loopdev, False, False, True)


Test patch: