Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions emhttp/plugins/dynamix.vm.manager/VMSettings.page
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ $libvirt_log = file_exists("/var/log/libvirt/libvirtd.log");
<input type="hidden" name="#file" value="<?=htmlspecialchars($domain_cfgfile)?>">
<input type="hidden" name="#command" value="/plugins/dynamix/scripts/emcmd">
<input type="hidden" name="#arg[1]" value="cmdStatus=Apply">
<input type="hidden" id="OLD_IMAGE_FILE" name="OLD_IMAGE_FILE" value="<?=htmlspecialchars($domain_cfg['IMAGE_FILE']);?>">
_(Enable VMs)_:
: <select id="SERVICE" name="SERVICE">
<?= mk_option($libvirt_service, 'disable', _('No'))?>
Expand Down Expand Up @@ -97,6 +98,7 @@ _(Disable Autostart/Start option for VMs)_:
<?if ($libvirt_up):?>
<?$libvirt_info = libvirt_version('libvirt')?>
<?$qemu_info = $lv->get_connect_information()?>

_(Libvirt version)_:
: <?=$libvirt_info['libvirt.major'].'.'.$libvirt_info['libvirt.minor'].'.'.$libvirt_info['libvirt.release']?>

Expand All @@ -108,24 +110,38 @@ _(Libvirt storage location)_:

:vms_libvirt_volume_help:

_(Libvirt secondary storage location)_:
: <?=htmlspecialchars($domain_cfg['IMAGE_FILE_SECONDARY'])."Test"?>

:vms_libvirt_secondary_volume_help:


<?else: /* Libvirt is stopped */ ?>
_(Libvirt vdisk size)_:
: <input type="number" id="IMAGE_SIZE" name="IMAGE_SIZE" min="1" value="<?=htmlspecialchars($domain_cfg['IMAGE_SIZE']);?>" style="width:50px;" required="required" />_(GB)_ <span id="SIZE_ERROR" class="errortext"></span>

:vms_libvirt_vdisk_size_help:

_(Libvirt storage location)_:
: <input type="text" id="IMAGE_FILE" name="IMAGE_FILE" autocomplete="off" spellcheck="false" value="<?=htmlspecialchars($domain_cfg['IMAGE_FILE']);?>" placeholder="e.g. /mnt/user/system/libvirt/libvirt.img" data-pickcloseonfile="true" data-pickfilter="img" data-pickroot="/mnt" data-pickfolders="true" required pattern="^[^\\]*libvirt\.img$">
: <input type="text" id="IMAGE_FILE" name="IMAGE_FILE" autocomplete="off" spellcheck="false" value="<?=htmlspecialchars($domain_cfg['IMAGE_FILE']);?>" placeholder="e.g. /mnt/user/system/libvirt/libvirt.img" data-pickcloseonfile="true" data-pickfilter="img" data-pickroot="/mnt" data-pickfolders="true" >
<?if (file_exists($domain_cfg['IMAGE_FILE'])):?><span id="deletePanel"><label><input type="checkbox" id="deleteCheckbox" /> _(Delete Image File)_</label></span><?endif;?>
<?if (!$started):?><span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
<?elseif (!is_dir(dirname($domain_cfg['IMAGE_FILE']))):?><span><i class="fa fa-warning icon warning"></i> _(Path does not exist)_</span>
<?endif;?><span id="IMAGE_ERROR" class="errortext"></span>

:vms_libvirt_location_help:

_(Libvirt secondary storage location)_:
: <input type="text" id="IMAGE_FILE_SECONDARY" name="IMAGE_FILE_SECONDARY" autocomplete="off" spellcheck="false" value="<?=htmlspecialchars($domain_cfg['IMAGE_FILE_SECONDARY']);?>" placeholder="e.g. /mnt/user/system/libvirt/libvirt.img" data-pickcloseonfile="true" data-pickfilter="img" data-pickroot="/mnt" data-pickfolders="true" >
<?if (!$started):?><span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
<?elseif (!is_dir(dirname($domain_cfg['IMAGE_FILE_SECONDARY']))):?><span><i class="fa fa-warning icon warning"></i> _(Path does not exist)_</span>
<?endif;?><span id="IMAGE_ERROR_SECONDARY" class="errortext"></span>

:vms_libvirt_secondary_location_help:

<?endif;?>
_(Default VM storage path)_:
: <input type="text" id="domaindir" name="DOMAINDIR" autocomplete="off" spellcheck="false" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="/mnt" value="<?=htmlspecialchars($domain_cfg['DOMAINDIR'])?>" placeholder="_(Click to Select)_" pattern="^[^\\]*/$">
: <input type="text" id="domaindir" name="DOMAINDIR" autocomplete="off" spellcheck="false" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="<?=is_dir('/mnt/user')?'/mnt/user':'/mnt'?>" value="<?=htmlspecialchars($domain_cfg['DOMAINDIR'])?>" placeholder="_(Click to Select)_" pattern="^[^\\]*/$">
<?if (!$started):?><span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
<?elseif (!is_dir($domain_cfg['DOMAINDIR'])):?><span><i class="fa fa-warning icon warning"></i> _(Path does not exist)_</span><?endif;?>

Expand Down Expand Up @@ -231,7 +247,7 @@ _(VFIO allow unsafe interrupts)_:

<?endif;?>

<?if ($libvirt_up && trim(shell_exec('stat -c %T -f /etc/libvirt'))=='btrfs'):?>
<?if ($libvirt_up && trim(shell_exec('stat -c %T -f /etc/libvirt'))=='btrfs' && strpos($domain_cfg['IMAGE_FILE'],".img")) :?>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix imprecise string matching for .img check.

The condition strpos($domain_cfg['IMAGE_FILE'],".img") has potential issues:

  1. It will return false when ".img" is at position 0, though this is unlikely for a path
  2. More importantly, it will match ".img" appearing anywhere in the path, including in directory names (e.g., /mnt/user/my.images/libvirt/ or /path/.img-backup/libvirt/)

Since the intent is to show this section only for image files (not directories), consider using a more precise check:

-<?if ($libvirt_up && trim(shell_exec('stat -c %T -f /etc/libvirt'))=='btrfs' && strpos($domain_cfg['IMAGE_FILE'],".img")) :?>
+<?if ($libvirt_up && trim(shell_exec('stat -c %T -f /etc/libvirt'))=='btrfs' && substr($domain_cfg['IMAGE_FILE'], -4) === '.img') :?>

This checks if the path ends with .img rather than containing it anywhere.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<?if ($libvirt_up && trim(shell_exec('stat -c %T -f /etc/libvirt'))=='btrfs' && strpos($domain_cfg['IMAGE_FILE'],".img")) :?>
<?if ($libvirt_up && trim(shell_exec('stat -c %T -f /etc/libvirt'))=='btrfs' && substr($domain_cfg['IMAGE_FILE'], -4) === '.img') :?>
🤖 Prompt for AI Agents
In emhttp/plugins/dynamix.vm.manager/VMSettings.page around line 303, the
current check strpos($domain_cfg['IMAGE_FILE'],".img") is imprecise (matches
anywhere and fails when at position 0); replace it with an end-of-string match
to ensure the path is an image file — for example, use a case-insensitive regex
or a suffix check such as preg_match('/\.img$/i',
trim($domain_cfg['IMAGE_FILE'])) or
strtolower(substr(trim($domain_cfg['IMAGE_FILE']), -4)) === '.img' so only paths
that actually end with ".img" trigger the condition.

<div class="advanced" markdown="1">
<div class="title"><span class="left"><i class="title fa fa-list"></i>_(Libvirt volume info)_</span></div>
_(btrfs filesystem show)_:
Expand Down Expand Up @@ -426,6 +442,9 @@ $(function(){
$("#IMAGE_FILE").fileTreeAttach(null, null, function(folder) {
$("#IMAGE_FILE").val(folder + 'libvirt.img').change();
});
$("#IMAGE_FILE_SECONDARY").fileTreeAttach(null, null, function(folder) {
$("#IMAGE_FILE_SECONDARY").val(folder + 'libvirt.img').change();
});
$('#domaindir').fileTreeAttach();
$('#mediadir').fileTreeAttach();
$('#winvirtio').fileTreeAttach();
Expand All @@ -449,6 +468,7 @@ $(function(){
$("#SERVICE").prop("disabled", checked).val('disable');
$("#IMAGE_SIZE").prop("disabled", checked);
$("#IMAGE_FILE").prop("disabled", checked).val("<?=$domain_cfg['IMAGE_FILE']?>");
$("#IMAGE_FILE_SECONDARY").prop("disabled");
$("#domaindir").prop("disabled", checked);
$("#mediadir").prop("disabled", checked);
$("#winvirtio_select").prop("disabled", checked);
Expand Down
58 changes: 58 additions & 0 deletions emhttp/plugins/dynamix.vm.manager/scripts/libvirt_init
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,64 @@
# run & log functions
. /etc/rc.d/rc.runlog


# Sync domain data if IMAGE_FILE and OLD_IMAGE_FILE differ
DOMAIN_CFG=/boot/config/domain.cfg

# Read values from domain.cfg
eval $(grep -E '^(IMAGE_FILE|OLD_IMAGE_FILE)=' "$DOMAIN_CFG")

# Remove quotes
IMAGE_FILE="${IMAGE_FILE%\"}"
IMAGE_FILE="${IMAGE_FILE#\"}"
OLD_IMAGE_FILE="${OLD_IMAGE_FILE%\"}"
OLD_IMAGE_FILE="${OLD_IMAGE_FILE#\"}"

# Proceed only if both variables are set and OLD_IMAGE_FILE exists
if [ -n "$IMAGE_FILE" ] && [ -n "$OLD_IMAGE_FILE" ] && [ "$IMAGE_FILE" != "$OLD_IMAGE_FILE" ]; then
if [ ! -e "$OLD_IMAGE_FILE" ]; then
log "OLD_IMAGE_FILE not found: $OLD_IMAGE_FILE — skipping sync"
else
log "IMAGE_FILE and OLD_IMAGE_FILE differ, syncing..."

TMP_MNT=/etc/libvirt-sync
IMG_FILE_NAME=$(basename "$IMAGE_FILE")
OLD_IMG_FILE_NAME=$(basename "$OLD_IMAGE_FILE")
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

if [[ "$OLD_IMAGE_FILE" == *.img ]]; then
# Backup image before mounting
BACKUP_PATH="${OLD_IMAGE_FILE%.img}.bak-${TIMESTAMP}.img"
log "Creating backup of OLD_IMAGE_FILE: $BACKUP_PATH"
cp -p "$OLD_IMAGE_FILE" "$BACKUP_PATH"

log "Mounting $OLD_IMAGE_FILE to $TMP_MNT"
mkdir -p "$TMP_MNT"
mount "$OLD_IMAGE_FILE" "$TMP_MNT"
log "Copying full contents from image to directory $IMAGE_FILE"
rsync -a --exclude="$OLD_IMG_FILE_NAME" "$TMP_MNT/" "$IMAGE_FILE/"
umount "$TMP_MNT"
elif [[ "$IMAGE_FILE" == *.img ]]; then
log "Mounting $IMAGE_FILE to $TMP_MNT"
mkdir -p "$TMP_MNT"
mount "$IMAGE_FILE" "$TMP_MNT"
log "Copying full contents from directory $OLD_IMAGE_FILE to image"
rsync -a --exclude="$IMG_FILE_NAME" --exclude='*.bak-*.img' "$OLD_IMAGE_FILE/" "$TMP_MNT/"
umount "$TMP_MNT"
else
log "Both IMAGE_FILE and OLD_IMAGE_FILE are directories, copying full contents"
rsync -a --exclude="$IMG_FILE_NAME" "$OLD_IMAGE_FILE/" "$IMAGE_FILE/"
fi

# Update OLD_IMAGE_FILE in domain.cfg
log "Updating OLD_IMAGE_FILE in $DOMAIN_CFG"
sed -i "s|^OLD_IMAGE_FILE=.*|OLD_IMAGE_FILE=\"$IMAGE_FILE\"|" "$DOMAIN_CFG"
fi
else
log "IMAGE_FILE and OLD_IMAGE_FILE match, or one is unset — skipping sync"
fi


# missing qemu directory would indicate new libvirt image file created
if [ ! -d /etc/libvirt/qemu ]; then
log "initializing /etc/libvirt"
Expand Down
3 changes: 2 additions & 1 deletion emhttp/plugins/dynamix.vm.manager/scripts/libvirtconfig
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
$cfgfile = "/boot/config/domain.cfg";
$cfg_defaults = [
"SERVICE" => "disable",
"IMAGE_FILE" => "/mnt/user/system/libvirt/libvirt.img",
"IMAGE_FILE" => "/mnt/user/system/libvirt/",
"OLD_IMAGE_FILE" => "/mnt/user/system/libvirt/",
"IMAGE_SIZE" => "1",
"DEBUG" => "no",
"DOMAINDIR" => "/mnt/user/domains/",
Expand Down