We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Unable to mount build-overlay with incremental builds
I am having issues getting incremental builds to run as the build overlay fails to mount. This is on Arch Linux with a btrfs filesystem, using mkosi commit 7030d76 (the latest as of the time of writing).

Using the following mkosi configuration:

```ini
[Distribution]
Distribution=debian
Release=bookworm

[Output]
CacheDirectory=cache
ImageId=test

[Content]
BuildPackages=
  make
BuildScript=build.sh
```

And a simple build script:

```bash
#!/usr/bin/bash
echo "building"
make --version
```

On the first run of `mkosi -i -f` everything runs successfully and I get an output image. Of potential interest is the dmesg output:

```console
[ 1907.326051] overlayfs: fs on '<path>/.mkosi-tmp7x3kcw9q/root' does not support file handles, falling back to index=off,nfs_export=off.
[ 1907.326054] overlayfs: fs on '<path>/.mkosi-tmp7x3kcw9q/root' does not support file handles, falling back to xino=off.
```

However, on a second run of `mkosi -i -f` I get the following error:

```console
‣ Removing output files…
‣ Building default image
Create subvolume '<path>/.mkosi-tmp38ur8lqj/root'
‣  Copying cached trees
‣  Running build script…
mount: <path>/.mkosi-tmp38ur8lqj/root: mount(2) system call failed: Stale file handle.
       dmesg(1) may have more information after failed mount system call.
‣ "mount --no-mtab overlay <path>/.mkosi-tmp38ur8lqj/root --types overlay --options lowerdir=<path>/.mkosi-tmp38ur8lqj/root,upperdir=<path>/.mkosi-tmp38ur8lqj/build-overlay,workdir=<path>/.mkosi-tmp38ur8lqj/build-overlay-workdirpawin893,userxattr" returned non-zero exit code 32.
umount: <path>/.mkosi-tmp38ur8lqj/root: not mounted.
‣ "umount --no-mtab <path>/.mkosi-tmp38ur8lqj/root" returned non-zero exit code 32.
‣   (Cleaning up overlayfs)
‣    (Removing overlay whiteout files…)
```

The  corresponding dmesg output is:
```console
[ 2126.464825] overlayfs: failed to verify origin (.mkosi-tmp38ur8lqj/root, ino=256, err=-116)
[ 2126.464829] overlayfs: failed to verify upper root origin
```

From [the overlayfs docs](https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#sharing-and-copying-layers)
> Mounting an overlay using an upper layer path, where the upper layer path was previously used by another mounted overlay in combination with a different lower layer path, is allowed, unless the "inodes index" feature or "metadata only copy up" feature is enabled.

It seems that Arch [has these features enabled by default](https://gitlab.archlinux.org/archlinux/packaging/packages/linux/-/blob/754a4cb9f87cb074eedbb6bebf1f926543a5b64f/config#L10193), and I am guessing that the use of a new temporary directory for the lower /root on each run causes a mismatch with the one used when the build overlay was created.

If I modify line 84 of `mkosi/mounts.py`
https://github.com/systemd/mkosi/blob/7030d76ae957d35d5b49e72c54c1d7f771cc4cac/mkosi/mounts.py#L84

to

```python
        options = [f"lowerdir={lower}" for lower in lowerdirs] + [f"upperdir={upperdir}", f"workdir={workdir}", "index=off"]
```

then the second incremental run works (it seems not to be neccessary to clean the cached images with `-ff`).  The dmesg warning from the first run about falling back to index=off seems to not apply to the build overlay.

Note that it wasn't neccessary (in my case, at least) to add `"metacopy=off"` to the options, although I guess this might be safer in general.

I am not sure if this is the proper fix; if so, I am happy to submit a pull request (or for a maintainer to just make the trivial change directly).

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mkosi/mounts.py`
Content:
```
1 # SPDX-License-Identifier: LGPL-2.1+
2 
3 import contextlib
4 import os
5 import platform
6 import stat
7 import tempfile
8 from collections.abc import Iterator, Sequence
9 from pathlib import Path
10 from typing import Optional
11 
12 from mkosi.log import complete_step
13 from mkosi.run import run
14 from mkosi.types import PathString
15 from mkosi.util import umask
16 from mkosi.versioncomp import GenericVersion
17 
18 
19 def stat_is_whiteout(st: os.stat_result) -> bool:
20     return stat.S_ISCHR(st.st_mode) and st.st_rdev == 0
21 
22 
23 def delete_whiteout_files(path: Path) -> None:
24     """Delete any char(0,0) device nodes underneath @path
25 
26     Overlayfs uses such files to mark "whiteouts" (files present in
27     the lower layers, but removed in the upper one).
28     """
29 
30     with complete_step("Removing overlay whiteout files…"):
31         for entry in path.rglob("*"):
32             # TODO: Use Path.stat() once we depend on Python 3.10+.
33             if stat_is_whiteout(os.stat(entry, follow_symlinks=False)):
34                 entry.unlink()
35 
36 
37 @contextlib.contextmanager
38 def mount(
39     what: PathString,
40     where: Path,
41     operation: Optional[str] = None,
42     options: Sequence[str] = (),
43     type: Optional[str] = None,
44     read_only: bool = False,
45     umount: bool = True,
46     lazy: bool = False,
47 ) -> Iterator[Path]:
48     if not where.exists():
49         with umask(~0o755):
50             where.mkdir(parents=True)
51 
52     if read_only:
53         options = ["ro", *options]
54 
55     cmd: list[PathString] = ["mount", "--no-mtab"]
56 
57     if operation:
58         cmd += [operation]
59 
60     cmd += [what, where]
61 
62     if type:
63         cmd += ["--types", type]
64 
65     if options:
66         cmd += ["--options", ",".join(options)]
67 
68     try:
69         run(cmd)
70         yield where
71     finally:
72         if umount:
73             run(["umount", "--no-mtab", *(["--lazy"] if lazy else []), where])
74 
75 
76 @contextlib.contextmanager
77 def mount_overlay(
78     lowerdirs: Sequence[Path],
79     upperdir: Path,
80     where: Path,
81     read_only: bool = True,
82 ) -> Iterator[Path]:
83     with tempfile.TemporaryDirectory(dir=upperdir.parent, prefix=f"{upperdir.name}-workdir") as workdir:
84         options = [f"lowerdir={lower}" for lower in lowerdirs] + [f"upperdir={upperdir}", f"workdir={workdir}"]
85 
86         # userxattr is only supported on overlayfs since kernel 5.11
87         if GenericVersion(platform.release()) >= GenericVersion("5.11"):
88             options.append("userxattr")
89 
90         try:
91             with mount("overlay", where, options=options, type="overlay", read_only=read_only):
92                 yield where
93         finally:
94             with complete_step("Cleaning up overlayfs"):
95                 delete_whiteout_files(upperdir)
96 
97 
98 @contextlib.contextmanager
99 def mount_usr(tree: Optional[Path], umount: bool = True) -> Iterator[None]:
100     if not tree:
101         yield
102         return
103 
104     # If we replace /usr, we should ignore any local modifications made to PATH as any of those binaries
105     # might not work anymore when /usr is replaced wholesale. We also make sure that both /usr/bin and
106     # /usr/sbin/ are searched so that e.g. if the host is Arch and the root is Debian we don't ignore the
107     # binaries from /usr/sbin in the Debian root.
108     old = os.environ["PATH"]
109     os.environ["PATH"] = "/usr/bin:/usr/sbin"
110 
111     try:
112         # If we mounted over /usr, trying to use umount will fail with "target is busy", because umount is
113         # being called from /usr, which we're trying to unmount. To work around this issue, we do a lazy
114         # unmount.
115         with mount(what=tree / "usr", where=Path("/usr"), operation="--bind", read_only=True, umount=umount, lazy=True):
116             yield
117     finally:
118         os.environ["PATH"] = old
119 
120 
121 @contextlib.contextmanager
122 def mount_passwd(name: str, uid: int, gid: int, umount: bool = True) -> Iterator[None]:
123     """
124     ssh looks up the running user in /etc/passwd and fails if it can't find the running user. To trick it, we
125     mount over /etc/passwd with our own file containing our user in the user namespace.
126     """
127     with tempfile.NamedTemporaryFile(prefix="mkosi.passwd", mode="w") as passwd:
128         passwd.write(f"{name}:x:{uid}:{gid}:{name}:/bin/sh\n")
129         os.fchown(passwd.file.fileno(), uid, gid)
130 
131         with mount(passwd.name, Path("/etc/passwd"), operation="--bind", umount=umount):
132             passwd.close() # Don't need the file anymore after it's mounted.
133             yield
134
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mkosi/mounts.py b/mkosi/mounts.py
--- a/mkosi/mounts.py
+++ b/mkosi/mounts.py
@@ -81,7 +81,19 @@
     read_only: bool = True,
 ) -> Iterator[Path]:
     with tempfile.TemporaryDirectory(dir=upperdir.parent, prefix=f"{upperdir.name}-workdir") as workdir:
-        options = [f"lowerdir={lower}" for lower in lowerdirs] + [f"upperdir={upperdir}", f"workdir={workdir}"]
+        options = [f"lowerdir={lower}" for lower in lowerdirs] + [
+            f"upperdir={upperdir}",
+            f"workdir={workdir}",
+            # Disable the inodes index and metacopy (only copy metadata upwards if possible)
+            # options. If these are enabled (e.g., if the kernel enables them by default),
+            # the mount will fail if the upper directory has been earlier used with a different
+            # lower directory, such as with a build overlay that was generated on top of a
+            # different temporary root.
+            # See https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#sharing-and-copying-layers
+            # and https://github.com/systemd/mkosi/issues/1841.
+            "index=off",
+            "metacopy=off"
+        ]
 
         # userxattr is only supported on overlayfs since kernel 5.11
         if GenericVersion(platform.release()) >= GenericVersion("5.11"):

{"golden_diff": "diff --git a/mkosi/mounts.py b/mkosi/mounts.py\n--- a/mkosi/mounts.py\n+++ b/mkosi/mounts.py\n@@ -81,7 +81,19 @@\n     read_only: bool = True,\n ) -> Iterator[Path]:\n     with tempfile.TemporaryDirectory(dir=upperdir.parent, prefix=f\"{upperdir.name}-workdir\") as workdir:\n-        options = [f\"lowerdir={lower}\" for lower in lowerdirs] + [f\"upperdir={upperdir}\", f\"workdir={workdir}\"]\n+        options = [f\"lowerdir={lower}\" for lower in lowerdirs] + [\n+            f\"upperdir={upperdir}\",\n+            f\"workdir={workdir}\",\n+            # Disable the inodes index and metacopy (only copy metadata upwards if possible)\n+            # options. If these are enabled (e.g., if the kernel enables them by default),\n+            # the mount will fail if the upper directory has been earlier used with a different\n+            # lower directory, such as with a build overlay that was generated on top of a\n+            # different temporary root.\n+            # See https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#sharing-and-copying-layers\n+            # and https://github.com/systemd/mkosi/issues/1841.\n+            \"index=off\",\n+            \"metacopy=off\"\n+        ]\n \n         # userxattr is only supported on overlayfs since kernel 5.11\n         if GenericVersion(platform.release()) >= GenericVersion(\"5.11\"):\n", "issue": "Unable to mount build-overlay with incremental builds\nI am having issues getting incremental builds to run as the build overlay fails to mount. This is on Arch Linux with a btrfs filesystem, using mkosi commit 7030d76 (the latest as of the time of writing).\r\n\r\nUsing the following mkosi configuration:\r\n\r\n```ini\r\n[Distribution]\r\nDistribution=debian\r\nRelease=bookworm\r\n\r\n[Output]\r\nCacheDirectory=cache\r\nImageId=test\r\n\r\n[Content]\r\nBuildPackages=\r\n  make\r\nBuildScript=build.sh\r\n```\r\n\r\nAnd a simple build script:\r\n\r\n```bash\r\n#!/usr/bin/bash\r\necho \"building\"\r\nmake --version\r\n```\r\n\r\nOn the first run of `mkosi -i -f` everything runs successfully and I get an output image. Of potential interest is the dmesg output:\r\n\r\n```console\r\n[ 1907.326051] overlayfs: fs on '<path>/.mkosi-tmp7x3kcw9q/root' does not support file handles, falling back to index=off,nfs_export=off.\r\n[ 1907.326054] overlayfs: fs on '<path>/.mkosi-tmp7x3kcw9q/root' does not support file handles, falling back to xino=off.\r\n```\r\n\r\nHowever, on a second run of `mkosi -i -f` I get the following error:\r\n\r\n```console\r\n\u2023 Removing output files\u2026\r\n\u2023 Building default image\r\nCreate subvolume '<path>/.mkosi-tmp38ur8lqj/root'\r\n\u2023  Copying cached trees\r\n\u2023  Running build script\u2026\r\nmount: <path>/.mkosi-tmp38ur8lqj/root: mount(2) system call failed: Stale file handle.\r\n       dmesg(1) may have more information after failed mount system call.\r\n\u2023 \"mount --no-mtab overlay <path>/.mkosi-tmp38ur8lqj/root --types overlay --options lowerdir=<path>/.mkosi-tmp38ur8lqj/root,upperdir=<path>/.mkosi-tmp38ur8lqj/build-overlay,workdir=<path>/.mkosi-tmp38ur8lqj/build-overlay-workdirpawin893,userxattr\" returned non-zero exit code 32.\r\numount: <path>/.mkosi-tmp38ur8lqj/root: not mounted.\r\n\u2023 \"umount --no-mtab <path>/.mkosi-tmp38ur8lqj/root\" returned non-zero exit code 32.\r\n\u2023   (Cleaning up overlayfs)\r\n\u2023    (Removing overlay whiteout files\u2026)\r\n```\r\n\r\nThe  corresponding dmesg output is:\r\n```console\r\n[ 2126.464825] overlayfs: failed to verify origin (.mkosi-tmp38ur8lqj/root, ino=256, err=-116)\r\n[ 2126.464829] overlayfs: failed to verify upper root origin\r\n```\r\n\r\nFrom [the overlayfs docs](https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#sharing-and-copying-layers)\r\n> Mounting an overlay using an upper layer path, where the upper layer path was previously used by another mounted overlay in combination with a different lower layer path, is allowed, unless the \"inodes index\" feature or \"metadata only copy up\" feature is enabled.\r\n\r\nIt seems that Arch [has these features enabled by default](https://gitlab.archlinux.org/archlinux/packaging/packages/linux/-/blob/754a4cb9f87cb074eedbb6bebf1f926543a5b64f/config#L10193), and I am guessing that the use of a new temporary directory for the lower /root on each run causes a mismatch with the one used when the build overlay was created.\r\n\r\nIf I modify line 84 of `mkosi/mounts.py`\r\nhttps://github.com/systemd/mkosi/blob/7030d76ae957d35d5b49e72c54c1d7f771cc4cac/mkosi/mounts.py#L84\r\n\r\nto\r\n\r\n```python\r\n        options = [f\"lowerdir={lower}\" for lower in lowerdirs] + [f\"upperdir={upperdir}\", f\"workdir={workdir}\", \"index=off\"]\r\n```\r\n\r\nthen the second incremental run works (it seems not to be neccessary to clean the cached images with `-ff`).  The dmesg warning from the first run about falling back to index=off seems to not apply to the build overlay.\r\n\r\nNote that it wasn't neccessary (in my case, at least) to add `\"metacopy=off\"` to the options, although I guess this might be safer in general.\r\n\r\nI am not sure if this is the proper fix; if so, I am happy to submit a pull request (or for a maintainer to just make the trivial change directly).\n", "before_files": [{"content": "# SPDX-License-Identifier: LGPL-2.1+\n\nimport contextlib\nimport os\nimport platform\nimport stat\nimport tempfile\nfrom collections.abc import Iterator, Sequence\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom mkosi.log import complete_step\nfrom mkosi.run import run\nfrom mkosi.types import PathString\nfrom mkosi.util import umask\nfrom mkosi.versioncomp import GenericVersion\n\n\ndef stat_is_whiteout(st: os.stat_result) -> bool:\n    return stat.S_ISCHR(st.st_mode) and st.st_rdev == 0\n\n\ndef delete_whiteout_files(path: Path) -> None:\n    \"\"\"Delete any char(0,0) device nodes underneath @path\n\n    Overlayfs uses such files to mark \"whiteouts\" (files present in\n    the lower layers, but removed in the upper one).\n    \"\"\"\n\n    with complete_step(\"Removing overlay whiteout files\u2026\"):\n        for entry in path.rglob(\"*\"):\n            # TODO: Use Path.stat() once we depend on Python 3.10+.\n            if stat_is_whiteout(os.stat(entry, follow_symlinks=False)):\n                entry.unlink()\n\n\n@contextlib.contextmanager\ndef mount(\n    what: PathString,\n    where: Path,\n    operation: Optional[str] = None,\n    options: Sequence[str] = (),\n    type: Optional[str] = None,\n    read_only: bool = False,\n    umount: bool = True,\n    lazy: bool = False,\n) -> Iterator[Path]:\n    if not where.exists():\n        with umask(~0o755):\n            where.mkdir(parents=True)\n\n    if read_only:\n        options = [\"ro\", *options]\n\n    cmd: list[PathString] = [\"mount\", \"--no-mtab\"]\n\n    if operation:\n        cmd += [operation]\n\n    cmd += [what, where]\n\n    if type:\n        cmd += [\"--types\", type]\n\n    if options:\n        cmd += [\"--options\", \",\".join(options)]\n\n    try:\n        run(cmd)\n        yield where\n    finally:\n        if umount:\n            run([\"umount\", \"--no-mtab\", *([\"--lazy\"] if lazy else []), where])\n\n\n@contextlib.contextmanager\ndef mount_overlay(\n    lowerdirs: Sequence[Path],\n    upperdir: Path,\n    where: Path,\n    read_only: bool = True,\n) -> Iterator[Path]:\n    with tempfile.TemporaryDirectory(dir=upperdir.parent, prefix=f\"{upperdir.name}-workdir\") as workdir:\n        options = [f\"lowerdir={lower}\" for lower in lowerdirs] + [f\"upperdir={upperdir}\", f\"workdir={workdir}\"]\n\n        # userxattr is only supported on overlayfs since kernel 5.11\n        if GenericVersion(platform.release()) >= GenericVersion(\"5.11\"):\n            options.append(\"userxattr\")\n\n        try:\n            with mount(\"overlay\", where, options=options, type=\"overlay\", read_only=read_only):\n                yield where\n        finally:\n            with complete_step(\"Cleaning up overlayfs\"):\n                delete_whiteout_files(upperdir)\n\n\n@contextlib.contextmanager\ndef mount_usr(tree: Optional[Path], umount: bool = True) -> Iterator[None]:\n    if not tree:\n        yield\n        return\n\n    # If we replace /usr, we should ignore any local modifications made to PATH as any of those binaries\n    # might not work anymore when /usr is replaced wholesale. We also make sure that both /usr/bin and\n    # /usr/sbin/ are searched so that e.g. if the host is Arch and the root is Debian we don't ignore the\n    # binaries from /usr/sbin in the Debian root.\n    old = os.environ[\"PATH\"]\n    os.environ[\"PATH\"] = \"/usr/bin:/usr/sbin\"\n\n    try:\n        # If we mounted over /usr, trying to use umount will fail with \"target is busy\", because umount is\n        # being called from /usr, which we're trying to unmount. To work around this issue, we do a lazy\n        # unmount.\n        with mount(what=tree / \"usr\", where=Path(\"/usr\"), operation=\"--bind\", read_only=True, umount=umount, lazy=True):\n            yield\n    finally:\n        os.environ[\"PATH\"] = old\n\n\n@contextlib.contextmanager\ndef mount_passwd(name: str, uid: int, gid: int, umount: bool = True) -> Iterator[None]:\n    \"\"\"\n    ssh looks up the running user in /etc/passwd and fails if it can't find the running user. To trick it, we\n    mount over /etc/passwd with our own file containing our user in the user namespace.\n    \"\"\"\n    with tempfile.NamedTemporaryFile(prefix=\"mkosi.passwd\", mode=\"w\") as passwd:\n        passwd.write(f\"{name}:x:{uid}:{gid}:{name}:/bin/sh\\n\")\n        os.fchown(passwd.file.fileno(), uid, gid)\n\n        with mount(passwd.name, Path(\"/etc/passwd\"), operation=\"--bind\", umount=umount):\n            passwd.close() # Don't need the file anymore after it's mounted.\n            yield\n", "path": "mkosi/mounts.py"}], "after_files": [{"content": "# SPDX-License-Identifier: LGPL-2.1+\n\nimport contextlib\nimport os\nimport platform\nimport stat\nimport tempfile\nfrom collections.abc import Iterator, Sequence\nfrom pathlib import Path\nfrom typing import Optional\n\nfrom mkosi.log import complete_step\nfrom mkosi.run import run\nfrom mkosi.types import PathString\nfrom mkosi.util import umask\nfrom mkosi.versioncomp import GenericVersion\n\n\ndef stat_is_whiteout(st: os.stat_result) -> bool:\n    return stat.S_ISCHR(st.st_mode) and st.st_rdev == 0\n\n\ndef delete_whiteout_files(path: Path) -> None:\n    \"\"\"Delete any char(0,0) device nodes underneath @path\n\n    Overlayfs uses such files to mark \"whiteouts\" (files present in\n    the lower layers, but removed in the upper one).\n    \"\"\"\n\n    with complete_step(\"Removing overlay whiteout files\u2026\"):\n        for entry in path.rglob(\"*\"):\n            # TODO: Use Path.stat() once we depend on Python 3.10+.\n            if stat_is_whiteout(os.stat(entry, follow_symlinks=False)):\n                entry.unlink()\n\n\n@contextlib.contextmanager\ndef mount(\n    what: PathString,\n    where: Path,\n    operation: Optional[str] = None,\n    options: Sequence[str] = (),\n    type: Optional[str] = None,\n    read_only: bool = False,\n    umount: bool = True,\n    lazy: bool = False,\n) -> Iterator[Path]:\n    if not where.exists():\n        with umask(~0o755):\n            where.mkdir(parents=True)\n\n    if read_only:\n        options = [\"ro\", *options]\n\n    cmd: list[PathString] = [\"mount\", \"--no-mtab\"]\n\n    if operation:\n        cmd += [operation]\n\n    cmd += [what, where]\n\n    if type:\n        cmd += [\"--types\", type]\n\n    if options:\n        cmd += [\"--options\", \",\".join(options)]\n\n    try:\n        run(cmd)\n        yield where\n    finally:\n        if umount:\n            run([\"umount\", \"--no-mtab\", *([\"--lazy\"] if lazy else []), where])\n\n\n@contextlib.contextmanager\ndef mount_overlay(\n    lowerdirs: Sequence[Path],\n    upperdir: Path,\n    where: Path,\n    read_only: bool = True,\n) -> Iterator[Path]:\n    with tempfile.TemporaryDirectory(dir=upperdir.parent, prefix=f\"{upperdir.name}-workdir\") as workdir:\n        options = [f\"lowerdir={lower}\" for lower in lowerdirs] + [\n            f\"upperdir={upperdir}\",\n            f\"workdir={workdir}\",\n            # Disable the inodes index and metacopy (only copy metadata upwards if possible)\n            # options. If these are enabled (e.g., if the kernel enables them by default),\n            # the mount will fail if the upper directory has been earlier used with a different\n            # lower directory, such as with a build overlay that was generated on top of a\n            # different temporary root.\n            # See https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#sharing-and-copying-layers\n            # and https://github.com/systemd/mkosi/issues/1841.\n            \"index=off\",\n            \"metacopy=off\"\n        ]\n\n        # userxattr is only supported on overlayfs since kernel 5.11\n        if GenericVersion(platform.release()) >= GenericVersion(\"5.11\"):\n            options.append(\"userxattr\")\n\n        try:\n            with mount(\"overlay\", where, options=options, type=\"overlay\", read_only=read_only):\n                yield where\n        finally:\n            with complete_step(\"Cleaning up overlayfs\"):\n                delete_whiteout_files(upperdir)\n\n\n@contextlib.contextmanager\ndef mount_usr(tree: Optional[Path], umount: bool = True) -> Iterator[None]:\n    if not tree:\n        yield\n        return\n\n    # If we replace /usr, we should ignore any local modifications made to PATH as any of those binaries\n    # might not work anymore when /usr is replaced wholesale. We also make sure that both /usr/bin and\n    # /usr/sbin/ are searched so that e.g. if the host is Arch and the root is Debian we don't ignore the\n    # binaries from /usr/sbin in the Debian root.\n    old = os.environ[\"PATH\"]\n    os.environ[\"PATH\"] = \"/usr/bin:/usr/sbin\"\n\n    try:\n        # If we mounted over /usr, trying to use umount will fail with \"target is busy\", because umount is\n        # being called from /usr, which we're trying to unmount. To work around this issue, we do a lazy\n        # unmount.\n        with mount(what=tree / \"usr\", where=Path(\"/usr\"), operation=\"--bind\", read_only=True, umount=umount, lazy=True):\n            yield\n    finally:\n        os.environ[\"PATH\"] = old\n\n\n@contextlib.contextmanager\ndef mount_passwd(name: str, uid: int, gid: int, umount: bool = True) -> Iterator[None]:\n    \"\"\"\n    ssh looks up the running user in /etc/passwd and fails if it can't find the running user. To trick it, we\n    mount over /etc/passwd with our own file containing our user in the user namespace.\n    \"\"\"\n    with tempfile.NamedTemporaryFile(prefix=\"mkosi.passwd\", mode=\"w\") as passwd:\n        passwd.write(f\"{name}:x:{uid}:{gid}:{name}:/bin/sh\\n\")\n        os.fchown(passwd.file.fileno(), uid, gid)\n\n        with mount(passwd.name, Path(\"/etc/passwd\"), operation=\"--bind\", umount=umount):\n            passwd.close() # Don't need the file anymore after it's mounted.\n            yield\n", "path": "mkosi/mounts.py"}]}