PVE 虚拟机 Bitmap 检查与清理脚本
用于解决这篇文章遇到的问题https://t2.re/archives/1277
check_vm_bitmap.sh
#!/bin/bash
# PVE 虚拟机 Bitmap 检查与清理脚本
# 使用 qemu-img info 检测 bitmap(比 bitmap --list 更兼容)
# 规则:不会擅自关机,运行中的 VM 清理失败时只给出手动命令提示
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
echo "=== PVE 虚拟机 Bitmap 检查工具 ==="
echo ""
# 检查是否在 PVE 节点上运行
if ! command -v qm &> /dev/null; then
echo "错误:未找到 qm 命令,请在 PVE 节点上运行此脚本"
exit 1
fi
VM_LIST=$(qm list | tail -n +2 | awk '{print $1}')
if [ -z "$VM_LIST" ]; then
echo "未找到任何虚拟机"
exit 0
fi
for VMID in $VM_LIST; do
VM_NAME=$(qm config $VMID 2>/dev/null | grep '^name:' | cut -d' ' -f2-)
VM_NAME=${VM_NAME:-unknown}
VM_STATUS=$(qm list | awk -v id="$VMID" '$1==id {print $3}')
# 先收集该 VM 的所有 bitmap 信息
BITMAP_INFO=""
DISK_LIST=""
while IFS= read -r DISK_LINE; do
VOLUME=$(echo "$DISK_LINE" | cut -d':' -f2- | cut -d',' -f1 | tr -d ' ')
[ -z "$VOLUME" ] && continue
DISK_PATH=$(pvesm path "$VOLUME" 2>/dev/null)
[ -z "$DISK_PATH" ] && continue
[ ! -f "$DISK_PATH" ] && continue
# 使用 qemu-img info 检测 bitmap(用 sed 提取 bitmaps 区块)
DISK_INFO=$(qemu-img info "$DISK_PATH" 2>/dev/null)
if echo "$DISK_INFO" | grep -q "bitmaps:"; then
# 从 bitmaps: 到 Child node 之间提取所有 name: 行
BM_NAMES=$(echo "$DISK_INFO" | sed -n '/bitmaps:/,/^Child node/p' | grep "name:" | sed 's/.*name: //')
if [ -n "$BM_NAMES" ]; then
DISK_LIST="${DISK_LIST}${DISK_LINE}\n"
BITMAP_INFO="${BITMAP_INFO} 卷: $VOLUME\n"
BITMAP_INFO="${BITMAP_INFO} 路径: $DISK_PATH\n"
BITMAP_INFO="${BITMAP_INFO} Bitmap:\n"
while IFS= read -r BM_NAME; do
[ -z "$BM_NAME" ] && continue
BITMAP_INFO="${BITMAP_INFO} - $BM_NAME\n"
done <<< "$BM_NAMES"
BITMAP_INFO="${BITMAP_INFO}\n"
fi
fi
done <<< "$(qm config $VMID 2>/dev/null | grep -E '^(virtio|scsi|sata|ide|nvme)[0-9]+:' | grep -v 'none' | grep -v 'cdrom')"
if [ -n "$BITMAP_INFO" ]; then
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}[发现 Bitmap]${NC} VM $VMID - $VM_NAME (状态: $VM_STATUS)"
echo ""
printf "%b" "$BITMAP_INFO"
read -p "是否清理该虚拟机的 bitmap? (y/n): " ANSWER
echo ""
if [ "$ANSWER" = "y" ] || [ "$ANSWER" = "Y" ]; then
while IFS= read -r DISK_LINE; do
VOLUME=$(echo "$DISK_LINE" | cut -d':' -f2- | cut -d',' -f1 | tr -d ' ')
[ -z "$VOLUME" ] && continue
DISK_PATH=$(pvesm path "$VOLUME" 2>/dev/null)
[ -z "$DISK_PATH" ] && continue
[ ! -f "$DISK_PATH" ] && continue
# 重新提取 bitmap names 用于删除
DISK_INFO=$(qemu-img info "$DISK_PATH" 2>/dev/null)
BM_NAMES=$(echo "$DISK_INFO" | sed -n '/bitmaps:/,/^Child node/p' | grep "name:" | sed 's/.*name: //')
while IFS= read -r BM_NAME; do
[ -z "$BM_NAME" ] && continue
echo " 删除 [$VOLUME] 的 bitmap: $BM_NAME ..."
if qemu-img bitmap --remove "$DISK_PATH" "$BM_NAME" 2>/dev/null; then
echo -e " ${GREEN}成功删除${NC}"
else
echo -e " ${RED}删除失败${NC}"
if [ "$VM_STATUS" = "running" ]; then
echo -e " ${YELLOW}提示:虚拟机正在运行,bitmap 可能被锁定${NC}"
fi
echo -e " ${YELLOW}请手动执行以下命令:${NC}"
echo " qemu-img bitmap --remove \"$DISK_PATH\" \"$BM_NAME\""
fi
done <<< "$BM_NAMES"
done <<< "$(echo -e "$DISK_LIST")"
echo -e "${GREEN}VM $VMID 处理完成${NC}"
else
echo " 已跳过 VM $VMID"
fi
echo ""
fi
done
echo "=== 检查完成 ==="