Skip to content

QEMU compatible file locking#180

Open
arctic-alpaca wants to merge 8 commits into
cyberus-technology:gardenlinuxfrom
arctic-alpaca:qemu-compatible-locking
Open

QEMU compatible file locking#180
arctic-alpaca wants to merge 8 commits into
cyberus-technology:gardenlinuxfrom
arctic-alpaca:qemu-compatible-locking

Conversation

@arctic-alpaca

@arctic-alpaca arctic-alpaca commented Jul 6, 2026

Copy link
Copy Markdown

Introduces QEMU compatible file locking.
Please see the commit messages for additional information.

References:

Testing was done on customer infra and the fix is confirmed to fix the reported issues.

The default is set to QEMU compatible file locking to avoid the need for changes in libvirt. We can revert that at a later point and use libvirt to set the appropriate config. In the interest of speed, I favor setting the default for now.
The BLK_PERM_RESIZE permission will be added as a follow-up as to not delay the fix since testing showed the current implementation to be sufficient for the moment (https://github.com/cobaltcore-dev/cobaltcore/issues/627).

Fixes https://github.com/cobaltcore-dev/cobaltcore/issues/595

Libvirt Pipeline: https://gitlab.cyberus-technology.de/cyberus/cloud/libvirt/-/merge_requests/253

@arctic-alpaca arctic-alpaca force-pushed the qemu-compatible-locking branch 2 times, most recently from f3b11b1 to 6763d5e Compare July 6, 2026 12:28

@phip1611 phip1611 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it is still in draft. I reviewed because my vacation starts in ~90min. I think this is looking good and is going into the right direction!

Thanks for the fix and the nice commit history!

Comment thread block/src/fcntl.rs
Comment thread block/src/fcntl.rs Outdated
Comment thread block/src/fcntl.rs
@arctic-alpaca arctic-alpaca force-pushed the qemu-compatible-locking branch 2 times, most recently from 4925935 to dac9921 Compare July 7, 2026 10:37
@arctic-alpaca arctic-alpaca marked this pull request as ready for review July 7, 2026 11:29
@arctic-alpaca arctic-alpaca requested review from Coffeeri and amphi July 7, 2026 11:29

@Coffeeri Coffeeri left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing PR, Julian! Also love the easy-to-follow refactoring commits.

Comment thread block/src/fcntl.rs
Comment thread virtio-devices/src/block.rs Outdated
Comment thread block/src/fcntl.rs
Comment thread block/src/fcntl.rs Outdated
Comment thread block/src/fcntl.rs Outdated
@arctic-alpaca arctic-alpaca force-pushed the qemu-compatible-locking branch from dac9921 to 3a963d2 Compare July 8, 2026 14:28
The function suffers from a TOCTOU problem. There is no guarantee that
the locks set on the file at the time of the state check are the same as
at the time of the locking attempt.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
Rust typically doesn't use `get_` prefixes.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
All functions take a `LockGranularity` already and making them part of
`LockGranularity` makes it easier to add associated helpers.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
By extracting the full file and file range locking, it's easier to add
file lock handling that's not following this pattern.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
Avoids repeating the retry/error logic when adding new users of the
function.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
Reference to pointer coercion doesn't happen for variadic functions like
`libc::fcntl`. While this doesn't pose a problem by itself, it removes
the check whether a reference can be coerced to the appropriate pointer
type or mutability. By explicitly casting to a pointer, we ensure
refactors don't accidentally break the safety requirements.

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
@arctic-alpaca arctic-alpaca force-pushed the qemu-compatible-locking branch from 3a963d2 to 0ccd243 Compare July 8, 2026 14:38
Comment thread block/src/fcntl.rs
Comment on lines +306 to +313
LockType::Read => vec![
LockGranularity::QemuCompatible
.flock(libc::F_WRLCK, QEMU_UNSHARE_LOCK_OFFSET + QEMU_WRITE_BYTE),
LockGranularity::QemuCompatible.flock(
libc::F_WRLCK,
QEMU_UNSHARE_LOCK_OFFSET + QEMU_CONSISTENT_READ_BYTE,
),
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LockType::Read => vec![
LockGranularity::QemuCompatible
.flock(libc::F_WRLCK, QEMU_UNSHARE_LOCK_OFFSET + QEMU_WRITE_BYTE),
LockGranularity::QemuCompatible.flock(
libc::F_WRLCK,
QEMU_UNSHARE_LOCK_OFFSET + QEMU_CONSISTENT_READ_BYTE,
),
],
LockType::Read => vec![
LockGranularity::QemuCompatible
.flock(libc::F_WRLCK, QEMU_LOCK_OFFSET + QEMU_WRITE_BYTE),
LockGranularity::QemuCompatible.flock(
libc::F_WRLCK,
QEMU_UNSHARE_LOCK_OFFSET + QEMU_CONSISTENT_READ_BYTE,
),
],

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean to check the write permission additionally to checking the unshared write permission?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked again with my AI friend:

TLDR: We should replace it, not add.

We got in the Read arm the two probes

  • byte 200 (QEMU_UNSHARE_LOCK_OFFSET + QEMU_CONSISTENT_READ_BYTE)
  • byte 201 (QEMU_UNSHARE_LOCK_OFFSET + QEMU_WRITE_BYTE)

QEMU does two things in raw_check_lock_bytes:

  1. For every permission it uses, it probes byte 200 + k: "does anyone forbid this permission?" [0].
    A reader uses consistent-read, so it probes byte 200. This matches our first probe.
  2. For every permission it does not share, it probes byte 100 + k: "does anyone use this permission?" [1].
    A reader does not share write, so it probes byte 101 (QEMU_LOCK_OFFSET + QEMU_WRITE_BYTE). Our second probe targets byte 201 instead.

Byte 201 answers a different question: "does anyone forbid write?". A reader never asks for write permission itself, so someone else forbidding write is no conflict for it. Worse, every other reader sets the flag on byte 201, because it also forbids write. Our F_WRLCK probe collides with that flag, so two read-only VMs could not use the same image. QEMU allows that.

Probing byte 101 instead still detects every writer, because every writer sets the flag on byte 101, whether it shares write or not.

[0] https://github.com/qemu/qemu/blob/c3a63b7c06ab3dae8cbe191e7cae9095022d37d6/block/file-posix.c#L949-L964
[1] https://github.com/qemu/qemu/blob/c3a63b7c06ab3dae8cbe191e7cae9095022d37d6/block/file-posix.c#L965-L980

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, should be fixed now 👍

QEMU uses locks on specific bytes for modeling file lock permissions.
This commit only implements what's needed to model the permissions
cloud-hypervisor currently uses. This is needed to be compatible with
storage management systems that expect QEMU compatible file locking like
NetApp's NFS implementation.

QEMU uses two offsets from the start of the file to separate permissions
and "unshared" permissions. "unshared" permissions are permissions that
cannot be shared between lock holders [0].
Starting from each offset, locks are placed with a length of one byte.
Each corresponds to a specific permission [1][2].

The locking is done by first signaling intent by locking the
required marker bytes. Next any conflicts with existing locks are
detected and in the failure case, the locks are rolled back.
Locking is done via `F_RDLCK`, which allows other parties to set the
same locks and check for conflicts with `F_WRLCK`.

[0]: https://github.com/qemu/qemu/blob/30e8a06b64aa58a3990ba39cb5d09531e7d265e0/block/file-posix.c#L131-L134
[1]: https://github.com/qemu/qemu/blob/30e8a06b64aa58a3990ba39cb5d09531e7d265e0/include/block/block-common.h#L392-L437
[2]: https://github.com/qemu/qemu/blob/30e8a06b64aa58a3990ba39cb5d09531e7d265e0/block/file-posix.c#L868-L940

On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
On-behalf-of: SAP julian.schindel@sap.com
Signed-off-by: Julian Schindel <julian.schindel@cyberus-technology.de>
@arctic-alpaca arctic-alpaca force-pushed the qemu-compatible-locking branch from 0ccd243 to b0d5ab2 Compare July 9, 2026 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants