[PATCH] lxc: truncate LOOP_GET_STATUS64.lo_file_name for long loop backing paths
LXC domains with long file-backed filesystem path fail to start when the backing image path is longer than LO_NAME_SIZE (64 bytes, 63 characters plus NUL). When long file path is passed, virFileLoopDeviceAssociate() -> virStrcpy() fails and user gets missleading error and domain fails to start. Example: <filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/demoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.raw'/> <target dir='/'/> </filesystem> To match losetup behavior we copy the path with virStrcpy() and allow truncation of lo_file_name only if needed, while still calling open() on the unchanged path. Finally log VIR_WARN when the path is expected to be truncated. But still report VIR_ERR_INTERNAL_ERROR for all other virStrcpy() failures. Fixes: https://gitlab.com/libvirt/libvirt/-/work_items/63 Signed-off-by: Radoslaw Smigielski <rsmigiel@redhat.com> --- src/util/virfile.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/util/virfile.c b/src/util/virfile.c index a0c6cb804862..ae33deb8d223 100644 --- a/src/util/virfile.c +++ b/src/util/virfile.c @@ -995,11 +995,18 @@ int virFileLoopDeviceAssociate(const char *file, lo.lo_flags = LO_FLAGS_AUTOCLEAR; - /* Set backing file name for LOOP_GET_STATUS64 queries */ + /* lo_file_name is loop device name, max length is LO_NAME_SIZE bytes. + * Truncate loop device name if file path is longer than LO_NAME_SIZE, + * and still use the full path to open backing file. */ if (virStrcpy((char *) lo.lo_file_name, file, LO_NAME_SIZE) < 0) { - virReportSystemError(errno, - _("Unable to set backing file %1$s"), file); - goto cleanup; + if (strlen(file) >= LO_NAME_SIZE) { + VIR_WARN("Loop backing device name %s truncated to %d bytes.", + file, LO_NAME_SIZE); + } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to set loop lo_file_name for %1$s"), file); + goto cleanup; + } } if ((fsfd = open(file, O_RDWR)) < 0) { -- 2.54.0
Please setup your git so that it formats the extra "From: " line which is needed as the mailing list mangles sender for domains having DMARC setup: https://www.libvirt.org/submitting-patches.html#git-configuration On Fri, May 29, 2026 at 13:00:22 +0200, Radoslaw Smigielski via Devel wrote:
LXC domains with long file-backed filesystem path fail to start when the backing image path is longer than LO_NAME_SIZE (64 bytes, 63 characters plus NUL). When long file path is passed, virFileLoopDeviceAssociate() -> virStrcpy() fails and user gets missleading error and domain fails to start.
Example:
<filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/demoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.raw'/> <target dir='/'/> </filesystem>
To match losetup behavior we copy the path with virStrcpy() and allow truncation of lo_file_name only if needed, while still calling open() on the unchanged path. Finally log VIR_WARN when the path is expected to be truncated. But still report VIR_ERR_INTERNAL_ERROR for all other virStrcpy() failures.
Umm, there are no other virStrcpy failures: /** * virStrcpy: * * @dest: destination buffer * @src: source buffer * @destbytes: number of bytes the destination can accommodate * * Copies @src to @dest. @dest is guaranteed to be 'nul' terminated if * destbytes is 1 or more. * * Returns: 0 on success, -1 if @src doesn't fit into @dest and was truncated. so what you have there is effectively dead code. Another issue is that if you'll have: <filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw'/> <target dir='/'/> </filesystem> <filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/b.raw'/> <target dir='/'/> </filesystem> They'll be truncated to the same string.
Fixes: https://gitlab.com/libvirt/libvirt/-/work_items/63
Signed-off-by: Radoslaw Smigielski <rsmigiel@redhat.com> --- src/util/virfile.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/util/virfile.c b/src/util/virfile.c index a0c6cb804862..ae33deb8d223 100644 --- a/src/util/virfile.c +++ b/src/util/virfile.c @@ -995,11 +995,18 @@ int virFileLoopDeviceAssociate(const char *file,
lo.lo_flags = LO_FLAGS_AUTOCLEAR;
- /* Set backing file name for LOOP_GET_STATUS64 queries */ + /* lo_file_name is loop device name, max length is LO_NAME_SIZE bytes. + * Truncate loop device name if file path is longer than LO_NAME_SIZE, + * and still use the full path to open backing file. */ if (virStrcpy((char *) lo.lo_file_name, file, LO_NAME_SIZE) < 0) { - virReportSystemError(errno, - _("Unable to set backing file %1$s"), file); - goto cleanup; + if (strlen(file) >= LO_NAME_SIZE) { + VIR_WARN("Loop backing device name %s truncated to %d bytes.", + file, LO_NAME_SIZE);
So these go only into the log. The question is if it here makes sense. In case when the user has just one truncated path which will thus never cause another error it will be forever ignored. Then if there are multiple paths, if they collide but work, why bother with warning? and if they don't work then reporting the error upfront is better.
+ } else { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("Unable to set loop lo_file_name for %1$s"), file); + goto cleanup;
This is dead code.
+ } }
if ((fsfd = open(file, O_RDWR)) < 0) { -- 2.54.0
On Fri, May 29, 2026 at 13:00:22 +0200, Radoslaw Smigielski via Devel wrote:
LXC domains with long file-backed filesystem path fail to start when the backing image path is longer than LO_NAME_SIZE (64 bytes, 63 characters plus NUL). When long file path is passed, virFileLoopDeviceAssociate() -> virStrcpy() fails and user gets missleading error and domain fails to start.
Example:
<filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/demoaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.raw'/> <target dir='/'/> </filesystem>
To match losetup behavior we copy the path with virStrcpy() and allow
of lo_file_name only if needed, while still calling open() on the unchanged path. Finally log VIR_WARN when the path is expected to be truncated. But still report VIR_ERR_INTERNAL_ERROR for all other virStrcpy() failures.
Umm, there are no other virStrcpy failures:
/** * virStrcpy: * * @dest: destination buffer * @src: source buffer * @destbytes: number of bytes the destination can accommodate * * Copies @src to @dest. @dest is guaranteed to be 'nul' terminated if * destbytes is 1 or more. * * Returns: 0 on success, -1 if @src doesn't fit into @dest and was
Hi Peter, Sure will fix my git config, sorry for that. On Fri, 29 May 2026 at 13:47, Peter Krempa <pkrempa@redhat.com> wrote: truncation truncated.
so what you have there is effectively dead code.
Indeed, this would need to be removed.
Another issue is that if you'll have:
<filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw'/> <target dir='/'/> </filesystem> <filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/b.raw'/> <target dir='/'/> </filesystem>
They'll be truncated to the same string.
So I tried to follow the same logic like losetup from util-linux uses to handle long paths: - takes the first 63 bytes of the absolute path - no intelligent path shortening (like keeping the filename and truncating middle) - adds an asterisk at position 62 to indicate the name was truncated lo->lo_file_name[LO_NAME_SIZE - 2] = '*'; // Position 62 gets '*' lo->lo_file_name[LO_NAME_SIZE - 1] = '\0'; // Position 63 is null terminator Above indeed can result in non-unique or unhelpful loop device names in the kernel's loop_info64 structure. Question if we should mimic the same logic or imlement someting smarter. Addint "*" before '\0' to indicate truncation would make it compatibile with losetup behavior. ---------------------- < Tℏanks | Radek >
On Fri, May 29, 2026 at 16:46:58 +0200, Radoslaw Smigielski wrote: [...]
Another issue is that if you'll have:
<filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw'/> <target dir='/'/> </filesystem> <filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/b.raw'/> <target dir='/'/> </filesystem>
They'll be truncated to the same string.
So I tried to follow the same logic like losetup from util-linux uses to handle long paths: - takes the first 63 bytes of the absolute path - no intelligent path shortening (like keeping the filename and truncating middle) - adds an asterisk at position 62 to indicate the name was truncated
lo->lo_file_name[LO_NAME_SIZE - 2] = '*'; // Position 62 gets '*' lo->lo_file_name[LO_NAME_SIZE - 1] = '\0'; // Position 63 is null terminator
Above indeed can result in non-unique or unhelpful loop device names in the kernel's loop_info64 structure.
So ... the most important question is how the value is used (which I don't remember any more). If it's visible or used from withint the LXC instance, then we must not change it. The users are out of luck unfortunately. If it's just informative and we can change it (and based on the fact that it's being truncated so it doesn't reflect anything real) we could also replace it by a controlled string which is unable to exceed 63 chars without truncation. It could be something like: 'libvirt-$UUID-$DEVALIAS'
Question if we should mimic the same logic or imlement someting smarter.
It really depends on what the semantics are; see above. If it can be changed though I'd go for something more stable for the future.
Addint "*" before '\0' to indicate truncation would make it compatibile with losetup behavior.
I'm not sure if that's too valuable of a behaviour. It prevents you from identifying the resource. Having an indentifier allowing you to look up the path would make more sense. ... Well, now you see why this issue lingered so long. There are plenty corner cases that need to be checked and behaviour analyzed :)
Uppsss I don't know how this happened but I missed above message. And in meantime I sent a v2 patch, please ignore.
On Mon, Jun 01, 2026 at 12:48:46 -0000, Radosław Śmigielski via Devel wrote:
Uppsss I don't know how this happened but I missed above message.
Well, it happened like this: You've send your v2 patch at: Date: Mon, 1 Jun 2026 14:14:51 +0200 but I've replied to v1 at: Date: Mon, 1 Jun 2026 14:31:53 +0200 So one could say that I've missed your v2, but my reply makes more sense to be against v1 anyways.
On Mon, 1 Jun 2026 at 14:32, Peter Krempa <pkrempa@redhat.com> wrote:
On Fri, May 29, 2026 at 16:46:58 +0200, Radoslaw Smigielski wrote:
[...]
Another issue is that if you'll have:
<filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source
file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw'/>
<target dir='/'/> </filesystem> <filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source
<target dir='/'/> </filesystem>
They'll be truncated to the same string.
So I tried to follow the same logic like losetup from util-linux uses to handle long paths: - takes the first 63 bytes of the absolute path - no intelligent path shortening (like keeping the filename and truncating middle) - adds an asterisk at position 62 to indicate the name was truncated
lo->lo_file_name[LO_NAME_SIZE - 2] = '*'; // Position 62 gets '*' lo->lo_file_name[LO_NAME_SIZE - 1] = '\0'; // Position 63 is null terminator
Above indeed can result in non-unique or unhelpful loop device names in
file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/b.raw'/> the
kernel's loop_info64 structure.
So ... the most important question is how the value is used (which I don't remember any more).
If it's visible or used from withint the LXC instance, then we must not change it. The users are out of luck unfortunately.
If it's just informative and we can change it (and based on the fact that it's being truncated so it doesn't reflect anything real) we could also replace it by a controlled string which is unable to exceed 63 chars without truncation.
It could be something like:
'libvirt-$UUID-$DEVALIAS'
Question if we should mimic the same logic or imlement someting smarter.
It really depends on what the semantics are; see above. If it can be changed though I'd go for something more stable for the future.
Addint "*" before '\0' to indicate truncation would make it compatibile with losetup behavior.
I'm not sure if that's too valuable of a behaviour. It prevents you from identifying the resource. Having an indentifier allowing you to look up the path would make more sense.
...
Well, now you see why this issue lingered so long. There are plenty corner cases that need to be checked and behaviour analyzed :)
So one more attempt ;) This time no patch but some short analysis and summary. There seems to be only one command which shows or use lo_file_name: losetup -l -O +REF All other commands or files in /proc or /dev do not contain lo_file_name. Example of the losetup command output goes like below, where last REF column is a value of lo_file_name: [root@rvm10 radek]# losetup -l -O +REF NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC REF /dev/loop1 0 0 1 0 /root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw 0 512 /root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee* /dev/loop0 0 0 1 0 /root/disk2.raw 0 512 /root/disk2.raw But important point here is that the lo_file_name/REF is being truncated above by losetup/kernel.
If it's visible or used from withint the LXC instance, then we must not change it. The users are out of luck unfortunately.
lo_file_name is not visible or meaningfully accessible from within the LXC container. In LXC container: 1. From /proc inside container: - /proc/partitions - Shows only device names like loop0, NOT the backing file path - /proc/mounts - Shows the mount point and device (/dev/loop0), NOT lo_file_name 2. From /sys inside container: - /sys/block/loopN/loop/backing_file - Shows the real path from the kernel's open file descriptor, NOT from lo_file_name - There is NO /sys/block/loopN/loop/ref or similar file that exposes lo_file_name 3. losetup inside the LXC domain (container) does not show values in REF column: sh-5.3# losetup -l -O +REF NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC REF /dev/loop1 (lost) 0 0 1 0 /root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw 0 512 /dev/loop0 (lost) 0 0 1 0 /root/disk2.raw 0 512 So making the long story short: - Linux kernel does NOT use lo_file_name to open or read the file. I/O goes through the fd passed to LOOP_SET_FD. - There is no code path where the kernel opens, resolves, or reads the backing file using lo_file_name. - LXC never reads or acts on lo_file_name. It is written once and left for the kernel and external tools. - The kernel's memcpy(lo->lo_file_name, info->lo_file_name, LO_NAME_SIZE) just stores it for LOOP_GET_STATUS64 queries - it's not used for I/O.
It could be something like:
'libvirt-$UUID-$DEVALIAS'
Perer, just to make sure I fully understood. What would be $DEVALIAS ? loop device name ? last 19 characters of the file path? DEVALIAS would need to fit into 19 characters. 64 - len('\0') - len('libvirt-$UUID') = 19 chars But I agree that this format of the name 'libvirt-$UUID-$DEVALIAS' would be easier for debugging and troubleshooting potential problems. We could fit into 63 characters. And finally it would be unique and deterministic. Because all above, it's much better approach than what I implemented with truncating and '*'. ---------------------- < Tℏanks | Radek >
On Fri, Jun 05, 2026 at 12:23:48 +0200, Radoslaw Smigielski via Devel wrote:
On Mon, 1 Jun 2026 at 14:32, Peter Krempa <pkrempa@redhat.com> wrote:
On Fri, May 29, 2026 at 16:46:58 +0200, Radoslaw Smigielski wrote:
[...]
Another issue is that if you'll have:
<filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source
file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw'/>
<target dir='/'/> </filesystem> <filesystem type='file' accessmode='passthrough'> <driver type='loop' format='raw'/> <source
<target dir='/'/> </filesystem>
They'll be truncated to the same string.
So I tried to follow the same logic like losetup from util-linux uses to handle long paths: - takes the first 63 bytes of the absolute path - no intelligent path shortening (like keeping the filename and truncating middle) - adds an asterisk at position 62 to indicate the name was truncated
lo->lo_file_name[LO_NAME_SIZE - 2] = '*'; // Position 62 gets '*' lo->lo_file_name[LO_NAME_SIZE - 1] = '\0'; // Position 63 is null terminator
Above indeed can result in non-unique or unhelpful loop device names in
file='/root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/b.raw'/> the
kernel's loop_info64 structure.
So ... the most important question is how the value is used (which I don't remember any more).
If it's visible or used from withint the LXC instance, then we must not change it. The users are out of luck unfortunately.
If it's just informative and we can change it (and based on the fact that it's being truncated so it doesn't reflect anything real) we could also replace it by a controlled string which is unable to exceed 63 chars without truncation.
It could be something like:
'libvirt-$UUID-$DEVALIAS'
Question if we should mimic the same logic or imlement someting smarter.
It really depends on what the semantics are; see above. If it can be changed though I'd go for something more stable for the future.
Addint "*" before '\0' to indicate truncation would make it compatibile with losetup behavior.
I'm not sure if that's too valuable of a behaviour. It prevents you from identifying the resource. Having an indentifier allowing you to look up the path would make more sense.
...
Well, now you see why this issue lingered so long. There are plenty corner cases that need to be checked and behaviour analyzed :)
So one more attempt ;) This time no patch but some short analysis and summary.
There seems to be only one command which shows or use lo_file_name: losetup -l -O +REF All other commands or files in /proc or /dev do not contain lo_file_name.
Example of the losetup command output goes like below, where last REF column is a value of lo_file_name:
[root@rvm10 radek]# losetup -l -O +REF NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC REF /dev/loop1 0 0 1 0 /root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw 0 512 /root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee* /dev/loop0 0 0 1 0 /root/disk2.raw 0 512 /root/disk2.raw
But important point here is that the lo_file_name/REF is being truncated above by losetup/kernel.
Okay, so it really seems to be just an identifier rather than the path itself.
If it's visible or used from withint the LXC instance, then we must not change it. The users are out of luck unfortunately.
lo_file_name is not visible or meaningfully accessible from within the LXC container. In LXC container:
1. From /proc inside container:
- /proc/partitions - Shows only device names like loop0, NOT the backing file path
- /proc/mounts - Shows the mount point and device (/dev/loop0), NOT lo_file_name 2. From /sys inside container: - /sys/block/loopN/loop/backing_file - Shows the real path from the kernel's open file descriptor, NOT from lo_file_name - There is NO /sys/block/loopN/loop/ref or similar file that exposes lo_file_name 3. losetup inside the LXC domain (container) does not show values in REF column:
sh-5.3# losetup -l -O +REF NAME SIZELIMIT OFFSET AUTOCLEAR RO BACK-FILE DIO LOG-SEC REF /dev/loop1 (lost) 0 0 1 0 /root/someveeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongpath/a.raw 0 512 /dev/loop0 (lost) 0 0 1 0 /root/disk2.raw 0 512
So making the long story short: - Linux kernel does NOT use lo_file_name to open or read the file. I/O goes through the fd passed to LOOP_SET_FD. - There is no code path where the kernel opens, resolves, or reads the backing file using lo_file_name. - LXC never reads or acts on lo_file_name. It is written once and left for the kernel and external tools. - The kernel's memcpy(lo->lo_file_name, info->lo_file_name, LO_NAME_SIZE) just stores it for LOOP_GET_STATUS64 queries - it's not used for I/O.
It could be something like:
'libvirt-$UUID-$DEVALIAS'
Perer, just to make sure I fully understood. What would be $DEVALIAS ? loop device name ? last 19 characters of the file path?
It's the alias of the device. Libvirt normally auto-allocates them at startup: <filesystem type='mount' accessmode='passthrough'> <driver type='virtiofs'/> <binary path='/usr/libexec/virtiofsd'/> <source dir='/tmp'/> <target dir='/'/> <readonly/> <alias name='fs0'/> ^^^^^ <address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/> </filesystem> it's stored in 'fs->info.alias'. Now as you see the alias is normally short but users can pass their own ...
DEVALIAS would need to fit into 19 characters. 64 - len('\0') - len('libvirt-$UUID') = 19 chars
... so this would effectively limit them to 19 characters. I guess that's an okay limitation because normally users don't set those. We should fail though if the user picks some massive string because we'd run into the same problem.
But I agree that this format of the name 'libvirt-$UUID-$DEVALIAS' would be easier for debugging and troubleshooting potential problems. We could fit into 63 characters. And finally it would be unique and deterministic.
Because all above, it's much better approach than what I implemented with truncating and '*'.
---------------------- < Tℏanks | Radek >
participants (3)
-
Peter Krempa -
Radoslaw Smigielski -
Radosław Śmigielski