当启动的系统检测到 HDD 时,如何避免任何类型的分区探测,从而仅检测基本驱动器功能(如型号、扇区大小和容量),从而仅创建一个设备文件/dev/sda
?是libata/内核问题吗?
这是关于硬盘损坏,分区表对应的扇区不应该被读取的数据恢复体验。我不是在谈论自动安装,它已经被禁用了。
链接到分区的扇区位于划痕区域。当头经过那里时,驱动器就会崩溃。
这是连接健康的 HDD 时会发生的情况:
消息输出
Oct 13 16:21:42 wks-01 kernel: [ 906.796660] sd 8:0:0:0: [sdb] 1953525167 512-byte logical blocks: (1.00 TB/931 GiB)...
Oct 13 16:21:42 wks-01 kernel: [ 906.915646] sdb: sdb1
(我需要禁用最后一个例程)
Udev 输出
KERNEL[906.915935] add /devices/pci0000:00/0000:00:1d.7/usb8/8-3/8-3:1.0/host8/target8:0:0/8:0:0:0/block/sdb (block)<br>
KERNEL[906.915999] add /devices/pci0000:00/0000:00:1d.7/usb8/8-3/8-3:1.0/host8/target8:0:0/8:0:0:0/block/sdb/**sdb1** (block) (**I need to disable this routine)** ... <br>
UDEV [907.392087] add /devices/pci0000:00/0000:00:1d.7/usb8/8-3/8-3:1.0/host8/target8:0:0/8:0:0:0/block/sdb/sdb1 (block)
答案1
更新:我发布了一个更快/更安全的方法以下使用内核模块
正如 @grochmal 所指出的,没有内置的方法可以做到这一点,尽管如果您愿意编译自己的内核,那么它非常简单:
在文件中的block/partition-generic.c
函数之前添加以下代码rescan_partitions
:
int do_partscan = 1;
static const struct kernel_param_ops do_partscan_param_ops = {
.set = param_set_int,
.get = param_get_int,
};
module_param_cb(do_partscan, &do_partscan_param_ops, &do_partscan, 0644);
并将此代码插入该函数的开头:
if (!do_partscan) {
bdev->bd_invalidated = 0;
return 0;
}
这将为您提供一个模块参数(/sys/module/...
您可以使用它来切换分区扫描。如果将其设置为0
任何扫描,则将立即返回且没有分区。如果需要,您可以将其翻转回1
并运行blockdev --rereadpt <device>
以加载分区附加磁盘。
[我不确定参数将在/sys/module
树中的位置 - 使用 查找它find /sys/module -name do_partscan
。我想你可以设置
#undef MODULE_PARAM_PREFIX
#define MODULE_PARAM_PREFIX "block."
module_param_cb ...
在将其放入的代码之前/sys/module/block/parameters/do_partscan
,但我还没有这样测试过。]
答案2
没有办法。
从 UDEV 的角度来看,分区 uevent 是直接从内核发送的,没有间接的。
从内核方面来看,事情发生在__blkdev_get()
它总是会读取至少一些分区表disk_get_part()
。这将读取足够的分区表以了解它是什么类型的分区表。
您所能做的就是CONFIG_MSDOS_PARTITION
在内核编译期间取消设置,以便msdos_partition()
它不会在内部使用check_partition()
。我不确定与disk_get_part()
此相比,它读取的分区有多少。
笔记
- 这是假设您的磁盘使用 MSDOS 分区。里面还有其他几个
CONFIG_*_PARTIOTION
参数/block/partitions/check.c
。 - 您需要从使用与要备份的分区类型不同的分区类型的驱动器引导该内核。这可能会很麻烦,也可能不会很麻烦(GPT 分区现在相当可行)。
- 另一种方式是,也许,你可以
rmmod scsi
。但这要求您不需要 SCSI 子系统来做任何事情。我认为实现这一目标的唯一方法是通过网络启动。然后,您可以连接损坏的磁盘,用于modprobe scsi
恢复子系统,并mknod
手动创建节点 ( )。这是假设的(我没有尝试过),我不确定是否mknod
不会__blkdev_get()
触发所有的努力。
答案3
我发现的最佳解决方案适用于内核2.6
及以上版本;它的工作原理是使用kretprobe
拦截add_disk
函数并设置一个阻止分区读取的标志。当函数返回时,标志恢复到其原始状态。这样,您可以稍后手动读取分区(使用partprobe
等)。
此方法还允许通过将状态设置为或/sys/module/no_partscan/parameters/enabled
来动态禁用/重新启用块。0
1
我已经在 上测试过AMD64
,但没有在x86
或上测试过ARM
。该代码依赖于选择与函数参数相对应的寄存器;这是按体系结构定义的,可能不正确。
这是该模块的代码:
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/genhd.h>
#ifdef CONFIG_X86_32
#define ARG1 ax
#define ARG2 bx
#elif defined CONFIG_X86_64
#define ARG1 di
#define ARG2 si
#elif defined CONFIG_ARM || CONFIG_ARM64
#define ARG1 regs[0]
#define ARG2 regs[1]
#endif
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,7,10)
#define ARG ARG1
static char func_name[NAME_MAX] = "add_disk";
#else // after 4.7.10, add_disk is a macro pointing to device_add_disk, which has the disk as its 2nd argument
#define ARG ARG2
static char func_name[NAME_MAX] = "device_add_disk";
#endif
static int enabled = 1;
module_param(enabled, int, 0664);
MODULE_PARM_DESC(enabled, "Enable intercepting disk initializing so we can block partscan.");
struct instance_data {
struct gendisk *disk;
};
static int entry_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct instance_data *data;
struct gendisk *disk;
data = (struct instance_data *)ri->data;
disk = (struct gendisk *)(regs->ARG);
if (!enabled || disk->flags & GENHD_FL_NO_PART_SCAN) {
data->disk = NULL;
} else {
pr_warn("Intercepted partition read for disk: %s.\n", disk->disk_name);
disk->flags |= (GENHD_FL_NO_PART_SCAN);
data->disk = disk; // store this so we can remove the NO_PARTSCAN flag on function return
}
return 0;
}
static int ret_handler(struct kretprobe_instance *ri, struct pt_regs *regs)
{
struct instance_data *data;
data = (struct instance_data *)ri->data;
if (data->disk)
data->disk->flags &= ~(GENHD_FL_NO_PART_SCAN);
return 0;
}
static struct kretprobe my_kretprobe = {
.handler = ret_handler,
.entry_handler = entry_handler,
.data_size = sizeof(struct instance_data),
.maxactive = 20,
};
static int __init kretprobe_init(void)
{
int ret;
my_kretprobe.kp.symbol_name = func_name;
ret = register_kretprobe(&my_kretprobe);
if (ret < 0) {
pr_warn("register_kretprobe failed, returned %d\n", ret);
return ret;
}
return 0;
}
static void __exit kretprobe_exit(void)
{
unregister_kretprobe(&my_kretprobe);
pr_info("kretprobe at unregistered\n");
/* nmissed > 0 suggests that maxactive was set too low. */
if (my_kretprobe.nmissed) pr_warn("Missed probing %d instances.\n", my_kretprobe.nmissed);
}
module_init(kretprobe_init)
module_exit(kretprobe_exit)
MODULE_LICENSE("GPL");
livepatch
它可以像其他答案中的模块一样构建和插入。
答案4
从 Linux 内核 4 及更高版本开始,您可以使用livepatch
模块来阻止读取分区表。这比重新编译整个内核更快更容易。我创建了一个模块,涵盖livepatch
使用宏的 api的各种形式IF
。
这已经5.13.0-28
在下进行了测试Ubuntu Server 20.10
。我没有尝试过其他内核 - 代码可能不正确。
这是no_partscan.c
:
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/livepatch.h>
#include <linux/fs.h>
#include <linux/genhd.h>
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5,4,179)
static int livepatch_rescan_partitions(struct gendisk *disk, struct block_device *bdev)
{
pr_warn("Intercepted partition read for disk: %s.\n", disk->disk_name);
return -EIO;
}
static struct klp_func funcs[] = {
{
.old_name = "rescan_partitions",
.new_func = livepatch_rescan_partitions,
}, { }
};
#else
static int livepatch_blk_add_partitions(struct gendisk *disk)
{
pr_warn("Intercepted partition read for disk: %s.\n", disk->disk_name);
return 0;
}
#endif
static struct klp_func funcs[] = {
{
.old_name = "blk_add_partitions",
.new_func = livepatch_blk_add_partitions,
}, { }
};
static struct klp_object objs[] = {
{
/* name being NULL means vmlinux */
.funcs = funcs,
}, { }
};
static struct klp_patch patch = {
.mod = THIS_MODULE,
.objs = objs,
};
#if LINUX_VERSION_CODE <= KERNEL_VERSION(5,0,21)
static int livepatch_init(void)
{
int ret;
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,15,18) && LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,12)
if (!klp_have_reliable_stack() && !patch.immediate) {
// Use of this option will also prevent removal of the patch.
// See Documentation/livepatch/livepatch.txt for more details.
patch.immediate = true;
pr_notice("The consistency model isn't supported for your architecture. Bypassing safety mechanisms and applying the patch immediately.\n");
}
#endif
ret = klp_register_patch(&patch);
if (ret)
return ret;
ret = klp_enable_patch(&patch);
if (ret) {
WARN_ON(klp_unregister_patch(&patch));
return ret;
}
return 0;
}
static void livepatch_exit(void)
{
#if LINUX_VERSION_CODE <= KERNEL_VERSION(4,11,12)
WARN_ON(klp_disable_patch(&patch));
#endif
WARN_ON(klp_unregister_patch(&patch));
}
#else
static int livepatch_init(void)
{
return klp_enable_patch(&patch);
}
static void livepatch_exit(void)
{
}
#endif
module_init(livepatch_init);
module_exit(livepatch_exit);
MODULE_LICENSE("GPL");
MODULE_INFO(livepatch, "Y");
它可以与以下内容一起使用Makefile
:
obj-m := no_partscan.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
(请注意,如果您在运行该 Makefile 时看到“缺少分隔符”,请用制表符替换四个空格。)
然后,当您准备好阻止分区读取时,请使用:
sudo insmod no_partscan
要禁用该补丁,请使用:
echo 0 > /sys/kernel/livepatch/no_partscan/enabled
这将删除补丁,但它仍然作为模块安装;您将必须再次运行sudo rmmod no_partscan.ko
然后insmod
等等才能重新启用它。