用于解决这篇文章遇到的问题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 "=== 检查完成 ==="

标签: none