fix bug45

This commit is contained in:
zj
2026-02-02 22:31:02 +08:00
parent ac809c08df
commit 3eecfc4db0
13 changed files with 651 additions and 290 deletions

View File

@@ -3,7 +3,7 @@ import subprocess
import json
import logging
import re
import os # 导入 os 模块
import os
logger = logging.getLogger(__name__)
@@ -82,7 +82,7 @@ class SystemInfoManager:
Helper to find device data by its path recursively.
Added type check for target_path.
"""
if not isinstance(target_path, str): # 添加类型检查
if not isinstance(target_path, str):
logger.warning(f"传入 _find_device_by_path_recursive 的 target_path 不是字符串: {target_path} (类型: {type(target_path)})")
return None
for dev in dev_list:
@@ -94,42 +94,109 @@ class SystemInfoManager:
return found
return None
def _find_device_by_maj_min_recursive(self, dev_list, target_maj_min):
"""
Helper to find device data by its major:minor number recursively.
"""
if not isinstance(target_maj_min, str):
logger.warning(f"传入 _find_device_by_maj_min_recursive 的 target_maj_min 不是字符串: {target_maj_min} (类型: {type(target_maj_min)})")
return None
for dev in dev_list:
if dev.get('maj:min') == target_maj_min:
return dev
if 'children' in dev:
found = self._find_device_by_maj_min_recursive(dev['children'], target_maj_min)
if found:
return found
return None
def get_mountpoint_for_device(self, device_path):
"""
根据设备路径获取其挂载点。
此方法现在可以正确处理 /dev/md/new_raid 这样的 RAID 阵列别名。
Added type check for device_path.
此方法现在可以正确处理 /dev/md/new_raid 这样的 RAID 阵列别名,以及 LVM 逻辑卷的符号链接
"""
if not isinstance(device_path, str): # 添加类型检查
if not isinstance(device_path, str):
logger.warning(f"传入 get_mountpoint_for_device 的 device_path 不是字符串: {device_path} (类型: {type(device_path)})")
return None
original_device_path = device_path
logger.debug(f"get_mountpoint_for_device: 正在处理原始设备路径: {original_device_path}")
devices = self.get_block_devices()
logger.debug(f"get_mountpoint_for_device: 从 lsblk 获取到 {len(devices)} 个块设备信息。")
target_maj_min = None
current_search_path = original_device_path
# 1. 解析符号链接以获取实际路径,并尝试获取 maj:min
if original_device_path.startswith('/dev/') and os.path.exists(original_device_path):
try:
# 先尝试解析真实路径,因为 stat 可能对符号链接返回链接本身的 maj:min
resolved_path = os.path.realpath(original_device_path)
logger.debug(f"get_mountpoint_for_device: 原始设备路径 {original_device_path} 解析为 {resolved_path}")
current_search_path = resolved_path # 使用解析后的路径进行 stat 和后续查找
# 获取解析后路径的 maj:min
stat_output, _ = self._run_command(["stat", "-c", "%t:%T", resolved_path], check_output=True)
raw_maj_min = stat_output.strip() # e.g., "fc:0"
# MODIFIED: Convert hex major to decimal
if ':' in raw_maj_min:
major_hex, minor_dec = raw_maj_min.split(':')
try:
major_dec = str(int(major_hex, 16)) # Convert hex to decimal string
target_maj_min = f"{major_dec}:{minor_dec}" # e.g., "252:0"
logger.debug(f"get_mountpoint_for_device: 解析后设备 {resolved_path} 的 maj:min (hex) 为 {raw_maj_min},转换为 decimal 为 {target_maj_min}")
except ValueError:
logger.warning(f"get_mountpoint_for_device: 无法将 maj:min 的主要部分 '{major_hex}' 从十六进制转换为十进制。")
target_maj_min = None # Fallback if conversion fails
else:
logger.warning(f"get_mountpoint_for_device: stat 输出 '{raw_maj_min}' 格式不符合预期。")
target_maj_min = None
except subprocess.CalledProcessError as e:
logger.debug(f"get_mountpoint_for_device: 无法获取 {resolved_path} 的 maj:min (命令失败): {e.stderr.strip()}")
except Exception as e:
logger.debug(f"get_mountpoint_for_device: 无法获取 {resolved_path} 的 maj:min (未知错误): {e}")
elif original_device_path.startswith('/dev/') and not os.path.exists(original_device_path):
logger.warning(f"get_mountpoint_for_device: 设备路径 {original_device_path} 不存在,无法获取挂载点。")
return None
# 2. 尝试使用 (可能已解析的) current_search_path 在 lsblk 输出中查找
dev_info = self._find_device_by_path_recursive(devices, current_search_path)
logger.debug(f"get_mountpoint_for_device: _find_device_by_path_recursive 查找结果 (使用 {current_search_path}): {dev_info}")
# 1. 尝试直接使用提供的 device_path 在 lsblk 输出中查找
dev_info = self._find_device_by_path_recursive(devices, device_path)
if dev_info:
logger.debug(f"直接从 lsblk 获取到 {device_path} 的挂载点: {dev_info.get('mountpoint')}")
return dev_info.get('mountpoint')
mountpoint = dev_info.get('mountpoint')
logger.debug(f"get_mountpoint_for_device: 直接从 lsblk 获取到 {original_device_path} (解析为 {current_search_path}) 的挂载点: {mountpoint}")
return mountpoint
# 2. 如果直接查找失败,并且是 RAID 阵列(例如 /dev/md/new_raid
if device_path.startswith('/dev/md'): # 此处 device_path 已通过类型检查
logger.debug(f"处理 RAID 阵列 {device_path} 以获取挂载点...")
# 3. 如果直接路径查找失败,并且我们有 maj:min尝试通过 maj:min 查找
if target_maj_min:
logger.debug(f"get_mountpoint_for_device: 直接路径查找失败,尝试通过 maj:min ({target_maj_min}) 查找。")
dev_info_by_maj_min = self._find_device_by_maj_min_recursive(devices, target_maj_min)
logger.debug(f"get_mountpoint_for_device: 通过 maj:min 查找结果: {dev_info_by_maj_min}")
if dev_info_by_maj_min:
mountpoint = dev_info_by_maj_min.get('mountpoint')
logger.debug(f"get_mountpoint_for_device: 通过 maj:min 找到 {original_device_path} 的挂载点: {mountpoint}")
return mountpoint
# 获取 RAID 阵列的实际内核设备路径(例如 /dev/md127
actual_md_device_path = self._get_actual_md_device_path(device_path)
logger.debug(f"RAID 阵列 {device_path} 的实际设备路径: {actual_md_device_path}")
# 4. 如果仍然查找失败,并且是 RAID 阵列(例如 /dev/md/new_raid
if original_device_path.startswith('/dev/md'):
logger.debug(f"get_mountpoint_for_device: 正在处理 RAID 阵列 {original_device_path} 以获取挂载点...")
actual_md_device_path = self._get_actual_md_device_path(original_device_path)
logger.debug(f"get_mountpoint_for_device: RAID 阵列 {original_device_path} 的实际设备路径: {actual_md_device_path}")
if actual_md_device_path:
# 现在,使用实际的内核设备路径从 lsblk 中查找挂载点
actual_dev_info = self._find_device_by_path_recursive(devices, actual_md_device_path)
logger.debug(f"实际设备路径 {actual_md_device_path} 的 lsblk 详情: {actual_dev_info}")
logger.debug(f"get_mountpoint_for_device: 实际设备路径 {actual_md_device_path} 的 lsblk 详情: {actual_dev_info}")
if actual_dev_info:
logger.debug(f"在实际设备 {actual_md_device_path} 上找到了挂载点: {actual_dev_info.get('mountpoint')}")
return actual_dev_info.get('mountpoint')
mountpoint = actual_dev_info.get('mountpoint')
logger.debug(f"get_mountpoint_for_device: 在实际设备 {actual_md_device_path} 上找到了挂载点: {mountpoint}")
return mountpoint
logger.debug(f"未能获取到 {device_path} 的挂载点。")
logger.debug(f"get_mountpoint_for_device: 未能获取到 {original_device_path} 的挂载点。")
return None
def get_mdadm_arrays(self):
@@ -269,9 +336,10 @@ class SystemInfoManager:
except Exception as e:
logger.error(f"获取LVM卷组信息失败: {e}")
# Get LVs
# Get LVs (MODIFIED: added -o lv_path)
try:
stdout, _ = self._run_command(["lvs", "--reportformat", "json"])
# 明确请求 lv_path因为默认的 --reportformat json 不包含它
stdout, _ = self._run_command(["lvs", "-o", "lv_name,vg_name,lv_uuid,lv_size,lv_attr,origin,snap_percent,lv_path", "--reportformat", "json"])
data = json.loads(stdout)
if 'report' in data and data['report']:
for lv_data in data['report'][0].get('lv', []):
@@ -283,7 +351,7 @@ class SystemInfoManager:
'lv_attr': lv_data.get('lv_attr'),
'origin': lv_data.get('origin'),
'snap_percent': lv_data.get('snap_percent'),
'lv_path': lv_data.get('lv_path')
'lv_path': lv_data.get('lv_path') # 现在应该能正确获取到路径了
})
except subprocess.CalledProcessError as e:
if "No logical volume found" in e.stderr or "No logical volumes found" in e.stdout:
@@ -358,7 +426,7 @@ class SystemInfoManager:
通过检查 /dev/md/ 目录下的符号链接来获取。
Added type check for array_path.
"""
if not isinstance(array_path, str): # 添加类型检查
if not isinstance(array_path, str):
logger.warning(f"传入 _get_actual_md_device_path 的 array_path 不是字符串: {array_path} (类型: {type(array_path)})")
return None
@@ -378,63 +446,103 @@ class SystemInfoManager:
def get_device_details_by_path(self, device_path):
"""
根据设备路径获取设备的 UUID 和文件系统类型 (fstype)。
此方法现在可以正确处理 /dev/md/new_raid 这样的 RAID 阵列别名。
此方法现在可以正确处理 /dev/md/new_raid 这样的 RAID 阵列别名,以及 LVM 逻辑卷的符号链接
Added type check for device_path.
:param device_path: 设备的路径,例如 '/dev/sdb1''/dev/md0''/dev/md/new_raid'
:return: 包含 'uuid''fstype' 的字典,如果未找到则返回 None。
"""
if not isinstance(device_path, str): # 添加类型检查
if not isinstance(device_path, str):
logger.warning(f"传入 get_device_details_by_path 的 device_path 不是字符串: {device_path} (类型: {type(device_path)})")
return None
original_device_path = device_path
logger.debug(f"get_device_details_by_path: 正在处理原始设备路径: {original_device_path}")
devices = self.get_block_devices() # 获取所有块设备信息
logger.debug(f"get_device_details_by_path: 从 lsblk 获取到 {len(devices)} 个块设备信息。")
# 1. 首先,尝试直接使用提供的 device_path 在 lsblk 输出中查找
# 对于 /dev/md/new_raid 这样的别名,这一步通常会返回 None
lsblk_details = self._find_device_by_path_recursive(devices, device_path)
logger.debug(f"lsblk_details for {device_path} (direct lookup): {lsblk_details}")
target_maj_min = None
current_search_path = original_device_path
if lsblk_details and lsblk_details.get('fstype'): # 即使没有UUID有fstype也算找到部分信息
# 如果直接找到了 fstype就返回 lsblk 提供的 UUID 和 fstype
# 这里的 UUID 应该是文件系统 UUID
logger.debug(f"直接从 lsblk 获取到 {device_path} 的详情: {lsblk_details}")
# 1. 解析符号链接以获取实际路径,并尝试获取 maj:min
if original_device_path.startswith('/dev/') and os.path.exists(original_device_path):
try:
resolved_path = os.path.realpath(original_device_path)
logger.debug(f"get_device_details_by_path: 原始设备路径 {original_device_path} 解析为 {resolved_path}")
current_search_path = resolved_path
stat_output, _ = self._run_command(["stat", "-c", "%t:%T", resolved_path], check_output=True)
raw_maj_min = stat_output.strip() # e.g., "fc:0"
# MODIFIED: Convert hex major to decimal
if ':' in raw_maj_min:
major_hex, minor_dec = raw_maj_min.split(':')
try:
major_dec = str(int(major_hex, 16)) # Convert hex to decimal string
target_maj_min = f"{major_dec}:{minor_dec}" # e.g., "252:0"
logger.debug(f"get_device_details_by_path: 解析后设备 {resolved_path} 的 maj:min (hex) 为 {raw_maj_min},转换为 decimal 为 {target_maj_min}")
except ValueError:
logger.warning(f"get_device_details_by_path: 无法将 maj:min 的主要部分 '{major_hex}' 从十六进制转换为十进制。")
target_maj_min = None
else:
logger.warning(f"get_device_details_by_path: stat 输出 '{raw_maj_min}' 格式不符合预期。")
target_maj_min = None
except subprocess.CalledProcessError as e:
logger.debug(f"get_device_details_by_path: 无法获取 {resolved_path} 的 maj:min (命令失败): {e.stderr.strip()}")
except Exception as e:
logger.debug(f"get_device_details_by_path: 无法获取 {resolved_path} 的 maj:min (未知错误): {e}")
elif original_device_path.startswith('/dev/') and not os.path.exists(original_device_path):
logger.warning(f"get_device_details_by_path: 设备路径 {original_device_path} 不存在,无法获取详情。")
return None
# 2. 首先,尝试使用 (可能已解析的) current_search_path 在 lsblk 输出中查找
lsblk_details = self._find_device_by_path_recursive(devices, current_search_path)
logger.debug(f"get_device_details_by_path: _find_device_by_path_recursive 查找结果 (使用 {current_search_path}, 直接查找): {lsblk_details}")
if lsblk_details and lsblk_details.get('fstype'):
logger.debug(f"get_device_details_by_path: 直接从 lsblk 获取到 {original_device_path} (解析为 {current_search_path}) 的详情: {lsblk_details}")
return {
'uuid': lsblk_details.get('uuid'),
'fstype': lsblk_details.get('fstype')
}
# 2. 如果直接查找失败,并且是 RAID 阵列(例如 /dev/md/new_raid
if device_path.startswith('/dev/md'): # 此处 device_path 已通过类型检查
logger.debug(f"处理 RAID 阵列 {device_path}...")
# 3. 如果直接路径查找失败,并且我们有 maj:min尝试通过 maj:min 查找
if target_maj_min:
logger.debug(f"get_device_details_by_path: 直接路径查找失败,尝试通过 maj:min ({target_maj_min}) 查找。")
dev_info_by_maj_min = self._find_device_by_maj_min_recursive(devices, target_maj_min)
logger.debug(f"get_device_details_by_path: 通过 maj:min 查找结果: {dev_info_by_maj_min}")
if dev_info_by_maj_min and dev_info_by_maj_min.get('fstype'):
logger.debug(f"get_device_details_by_path: 通过 maj:min 找到 {original_device_path} 的文件系统详情: {dev_info_by_maj_min}")
return {
'uuid': dev_info_by_maj_min.get('uuid'),
'fstype': dev_info_by_maj_min.get('fstype')
}
# 获取 RAID 阵列的实际内核设备路径(例如 /dev/md127
actual_md_device_path = self._get_actual_md_device_path(device_path)
logger.debug(f"RAID 阵列 {device_path} 的实际设备路径: {actual_md_device_path}")
# 4. 如果仍然没有找到,并且是 RAID 阵列(例如 /dev/md/new_raid
if original_device_path.startswith('/dev/md'):
logger.debug(f"get_device_details_by_path: 正在处理 RAID 阵列 {original_device_path}...")
actual_md_device_path = self._get_actual_md_device_path(original_device_path)
logger.debug(f"get_device_details_by_path: RAID 阵列 {original_device_path} 的实际设备路径: {actual_md_device_path}")
if actual_md_device_path:
# 现在,使用实际的内核设备路径从 lsblk 中查找 fstype 和 UUID (文件系统 UUID)
actual_device_lsblk_details = self._find_device_by_path_recursive(devices, actual_md_device_path)
logger.debug(f"实际设备路径 {actual_md_device_path} 的 lsblk 详情: {actual_device_lsblk_details}")
logger.debug(f"get_device_details_by_path: 实际设备路径 {actual_md_device_path} 的 lsblk 详情: {actual_device_lsblk_details}")
if actual_device_lsblk_details and actual_device_lsblk_details.get('fstype'):
# 找到了实际 RAID 设备上的文件系统信息
# 此时的 UUID 是文件系统 UUIDfstype 是文件系统类型
logger.debug(f"在实际设备 {actual_md_device_path} 上找到了文件系统详情: {actual_device_lsblk_details}")
logger.debug(f"get_device_details_by_path: 在实际设备 {actual_md_device_path} 上找到了文件系统详情: {actual_device_lsblk_details}")
return {
'uuid': actual_device_lsblk_details.get('uuid'),
'fstype': actual_device_lsblk_details.get('fstype')
}
else:
# RAID 设备存在,但 lsblk 没有报告文件系统 (例如,尚未格式化)
# 此时 fstype 为 None。如果需要我们可以返回 RAID 阵列本身的 UUID但 fstype 仍为 None
logger.warning(f"RAID 阵列 {device_path} (实际设备 {actual_md_device_path}) 未找到文件系统类型。")
# 对于 fstab如果没有 fstype就无法创建条目。
# 此时返回 None让调用者知道无法写入 fstab。
logger.warning(f"get_device_details_by_path: RAID 阵列 {original_device_path} (实际设备 {actual_md_device_path}) 未找到文件系统类型。")
return None
else:
logger.warning(f"无法确定 RAID 阵列 {device_path} 的实际内核设备路径。")
return None # 无法解析实际设备路径,也无法获取 fstype
logger.warning(f"get_device_details_by_path: 无法确定 RAID 阵列 {original_device_path} 的实际内核设备路径。")
return None
# 3. 如果仍然没有找到,返回 None
logger.debug(f"未能获取到 {device_path} 的任何详情。")
# 5. 如果仍然没有找到,返回 None
logger.debug(f"get_device_details_by_path: 未能获取到 {original_device_path} 的任何详情。")
return None