clone_image() takes $snap but never passed it to filesystem_path(), so it always resolved the live subvolume. Cloning from a snapshot therefore produced a clone of the CURRENT state while the caller believed it had cloned the snapshot.
This is reachable from the GUI and the API, not a dormant path: volume_has_feature() advertises 'clone' for the 'snap' key on raw volumes, and PVE::Storage::clone_image() passes the snapshot straight through, so cloning a VM from one of its snapshots on a btrfs storage silently gets current data. Nothing errors out and the clone is perfectly readable, which is what makes it easy to miss -- the disk simply holds different content than the snapshot it was named after. Reproduced on a loop-backed btrfs by writing known content, snapshotting, then overwriting the source: clone_image($snap) returned the post-overwrite md5. With $snap passed through it returns the snapshot's md5. Found while implementing copy-offload for this plugin, which needs the same distinction and is where the question came up. Generated-By: Claude (https://claude.ai) Signed-off-by: Ciro Iriarte <[email protected]> Co-Authored-By: Claude <[email protected]> --- src/PVE/Storage/BTRFSPlugin.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PVE/Storage/BTRFSPlugin.pm b/src/PVE/Storage/BTRFSPlugin.pm index fb47aa0..f58a1bb 100644 --- a/src/PVE/Storage/BTRFSPlugin.pm +++ b/src/PVE/Storage/BTRFSPlugin.pm @@ -298,7 +298,10 @@ sub clone_image { $imagedir .= "/$vmid"; mkpath $imagedir; - my $path = $class->filesystem_path($scfg, $volname); + # Clone the snapshot the caller asked for, not the live subvolume. Dropping $snap + # here silently produced a clone of the CURRENT state while the caller believed it + # had cloned the snapshot. + my $path = $class->filesystem_path($scfg, $volname, $snap); my $newname = $class->find_free_diskname($storeid, $scfg, $vmid, $format, 1); # For btrfs subvolumes we don't actually need the "link": -- 2.54.0
