From ffdf6bde9bb2aae6ac8834be8922b616ce5dd069 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 10 Mar 2026 10:39:04 +0530 Subject: [PATCH 1/2] Fix repository mount failure during restore due to path format handling Restore could fail when the backup repository address was specified in formats such as \\server\share. The restore logic built a raw shell command which caused backslashes to be interpreted as escape characters, resulting in an invalid mount path. Execute the mount command using Script.executePipedCommands() so the repository path is passed as an argument instead of being embedded in a shell command string. --- .../LibvirtRestoreBackupCommandWrapper.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java index 714e3844b347..b307fe4286ad 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java @@ -45,6 +45,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Objects; @@ -52,7 +53,6 @@ @ResourceWrapper(handles = RestoreBackupCommand.class) public class LibvirtRestoreBackupCommandWrapper extends CommandWrapper { private static final String BACKUP_TEMP_FILE_PREFIX = "csbackup"; - private static final String MOUNT_COMMAND = "sudo mount -t %s %s %s"; private static final String UMOUNT_COMMAND = "sudo umount %s"; private static final String FILE_PATH_PLACEHOLDER = "%s/%s"; private static final String ATTACH_QCOW2_DISK_COMMAND = " virsh attach-disk %s %s %s --driver qemu --subdriver qcow2 --cache none"; @@ -182,7 +182,7 @@ private void restoreVolume(KVMStoragePoolManager storagePoolMgr, String backupPa private String mountBackupDirectory(String backupRepoAddress, String backupRepoType, String mountOptions, Integer mountTimeout) { String randomChars = RandomStringUtils.random(5, true, false); - String mountDirectory = String.format("%s.%s",BACKUP_TEMP_FILE_PREFIX , randomChars); + String mountDirectory = String.format("%s.%s", BACKUP_TEMP_FILE_PREFIX, randomChars); try { mountDirectory = Files.createTempDirectory(mountDirectory).toString(); @@ -191,23 +191,35 @@ private String mountBackupDirectory(String backupRepoAddress, String backupRepoT throw new CloudRuntimeException("Failed to create the tmp mount directory for restore on the KVM host"); } - String mount = String.format(MOUNT_COMMAND, backupRepoType, backupRepoAddress, mountDirectory); - if ("cifs".equals(backupRepoType)) { + if ("cifs".equalsIgnoreCase(backupRepoType)) { if (Objects.isNull(mountOptions) || mountOptions.trim().isEmpty()) { mountOptions = "nobrl"; } else { mountOptions += ",nobrl"; } } + + List commands = new ArrayList<>(); + List cmd = new ArrayList<>(); + cmd.add("sudo"); + cmd.add("mount"); + cmd.add("-t"); + cmd.add(backupRepoType); + cmd.add(backupRepoAddress); + cmd.add(mountDirectory); if (Objects.nonNull(mountOptions) && !mountOptions.trim().isEmpty()) { - mount += " -o " + mountOptions; + cmd.add("-o"); + cmd.add(mountOptions); } + commands.add(cmd.toArray(new String[0])); - int exitValue = Script.runSimpleBashScriptForExitValue(mount, mountTimeout, false); - if (exitValue != 0) { - logger.error("Failed to mount repository {} of type {} to the directory {}", backupRepoAddress, backupRepoType, mountDirectory); + Pair result = Script.executePipedCommands(commands, mountTimeout); + if (result.first() != 0) { + logger.error("Failed to mount repository {} of type {} to the directory {}. Error: {}", + backupRepoAddress, backupRepoType, mountDirectory, result.second()); throw new CloudRuntimeException("Failed to mount the backup repository on the KVM host"); } + return mountDirectory; } From c7aa780d09a2488dd42731626b39d3676f630b0f Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Tue, 10 Mar 2026 14:26:24 +0530 Subject: [PATCH 2/2] Unit test fixes --- .../LibvirtRestoreBackupCommandWrapper.java | 9 +++++-- ...ibvirtRestoreBackupCommandWrapperTest.java | 25 ++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java index b307fe4286ad..f047777e7e85 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java @@ -199,6 +199,13 @@ private String mountBackupDirectory(String backupRepoAddress, String backupRepoT } } + executeMount(backupRepoAddress, backupRepoType, mountOptions, mountDirectory, mountTimeout); + + return mountDirectory; + } + + private void executeMount(String backupRepoAddress, String backupRepoType, String mountOptions, + String mountDirectory, Integer mountTimeout) { List commands = new ArrayList<>(); List cmd = new ArrayList<>(); cmd.add("sudo"); @@ -219,8 +226,6 @@ private String mountBackupDirectory(String backupRepoAddress, String backupRepoT backupRepoAddress, backupRepoType, mountDirectory, result.second()); throw new CloudRuntimeException("Failed to mount the backup repository on the KVM host"); } - - return mountDirectory; } private void unmountBackupDirectory(String backupDirectory) { diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java index d72dc0d8ac36..abbb3c1a174b 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapperTest.java @@ -18,6 +18,7 @@ import com.cloud.agent.api.Answer; import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.utils.Pair; import com.cloud.utils.script.Script; import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.backup.BackupAnswer; @@ -79,6 +80,8 @@ public void testExecuteWithVmExistsNull() throws Exception { filesMock.when(() -> Files.createTempDirectory(anyString())).thenReturn(tempPath); try (MockedStatic