%PDF- %PDF-
Direktori : /var/lib/dkms/blksnap/6.3.0.73/source/ |
Current File : //var/lib/dkms/blksnap/6.3.0.73/source/bdevfilter.c |
// SPDX-License-Identifier: GPL-2.0 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> #include <linux/ftrace.h> #include <linux/kprobes.h> #include <linux/sched/task.h> #include <linux/bio.h> #ifdef HAVE_GENHD_H #include <linux/genhd.h> #endif #include <linux/blkdev.h> #include <linux/list.h> #include "bdevfilter.h" #include "version.h" #if defined(BLK_SNAP_DEBUGLOG) #undef pr_debug #define pr_debug(fmt, ...) \ ({ \ printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__); \ }) #endif struct bdev_extension { struct list_head link; dev_t dev_id; #if defined(HAVE_BI_BDISK) struct gendisk *disk; u8 partno; #else struct block_device *bdev; #endif struct bdev_filter *bd_filters[bdev_filter_alt_end]; spinlock_t bd_filters_lock; }; /* The list of extensions for this block device */ static LIST_HEAD(bdev_extension_list); /* Lock the queue of block device to add or delete extension. */ static DEFINE_SPINLOCK(bdev_extension_list_lock); static inline struct bdev_extension *bdev_extension_find(dev_t dev_id) { struct bdev_extension *ext; if (list_empty(&bdev_extension_list)) return NULL; list_for_each_entry (ext, &bdev_extension_list, link) if (dev_id == ext->dev_id) return ext; return NULL; } #if defined(HAVE_BI_BDISK) static inline struct bdev_extension *bdev_extension_find_part(struct gendisk *disk, u8 partno) { struct bdev_extension *ext; if (list_empty(&bdev_extension_list)) return NULL; list_for_each_entry (ext, &bdev_extension_list, link) if ((disk == ext->disk) && (partno == ext->partno)) return ext; return NULL; } #else static inline struct bdev_extension *bdev_extension_find_bdev(struct block_device *bdev) { struct bdev_extension *ext; if (list_empty(&bdev_extension_list)) return NULL; list_for_each_entry (ext, &bdev_extension_list, link) if (bdev == ext->bdev) return ext; return NULL; } #endif static inline struct bdev_extension *bdev_extension_append(struct block_device *bdev) { bool recreate = false; struct bdev_extension *result = NULL; struct bdev_extension *ext; struct bdev_extension *ext_tmp; ext_tmp = kzalloc(sizeof(struct bdev_extension), GFP_NOIO); if (!ext_tmp) return NULL; INIT_LIST_HEAD(&ext_tmp->link); ext_tmp->dev_id = bdev->bd_dev; #if defined(HAVE_BI_BDISK) ext_tmp->disk = bdev->bd_disk; ext_tmp->partno = bdev->bd_partno; #else ext_tmp->bdev = bdev; #endif memset(ext_tmp->bd_filters, 0, sizeof(ext_tmp->bd_filters)); spin_lock_init(&ext_tmp->bd_filters_lock); spin_lock(&bdev_extension_list_lock); ext = bdev_extension_find(bdev->bd_dev); if (!ext) { /* add new extension */ pr_debug("Add new bdev extension"); list_add_tail(&ext_tmp->link, &bdev_extension_list); result = ext_tmp; ext_tmp = NULL; } else { #if defined(HAVE_BI_BDISK) if ((ext->disk == bdev->bd_disk) && (ext->partno == bdev->bd_partno)) { #else if (ext->bdev == bdev) { #endif /* extension already exist */ pr_debug("Bdev extension already exist"); result = ext; } else { /* extension should be recreated */ pr_debug("Bdev extension should be recreated"); list_add_tail(&ext_tmp->link, &bdev_extension_list); result = ext_tmp; recreate = true; list_del(&ext->link); ext_tmp = ext; } } spin_unlock(&bdev_extension_list_lock); /* Recreated block device found */ if (recreate) { enum bdev_filter_altitudes alt; pr_info("Detach all block device filters from %d:%d\n", MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); for (alt = 0; alt < bdev_filter_alt_end; alt++) { struct bdev_filter *flt; spin_lock(&ext_tmp->bd_filters_lock); flt = ext_tmp->bd_filters[alt]; ext_tmp->bd_filters[alt] = NULL; spin_unlock(&ext_tmp->bd_filters_lock); if (flt) bdev_filter_put(flt); } } kfree(ext_tmp); return result; } /** * bdev_filter_attach - Attach a filter to original block device. * @bdev: * block device * @name: * Name of the block device filter. * @altitude: * Number of the block device filter. * @flt: * Pointer to the filter structure. * * The bdev_filter_detach() function allows to detach the filter from the block * device. * * Return: * 0 - OK * -EBUSY - a filter on this altitude already exists * -EINVAL - invalid altitude */ int bdev_filter_attach(struct block_device *bdev, const char *name, const enum bdev_filter_altitudes altitude, struct bdev_filter *flt) { int ret = 0; struct bdev_extension *ext; pr_info("Attach block device filter '%s'", name); if ((altitude < 0) || (altitude >= bdev_filter_alt_end)) return -EINVAL; ext = bdev_extension_append(bdev); if (!ext) return -ENOMEM; spin_lock(&ext->bd_filters_lock); if (ext->bd_filters[altitude]) { pr_debug("filter busy. 0x%p", ext->bd_filters[altitude]); ret = -EBUSY; } else ext->bd_filters[altitude] = flt; spin_unlock(&ext->bd_filters_lock); if (!ret) pr_info("Block device filter '%s' has been attached to %d:%d", name, MAJOR(bdev->bd_dev), MINOR(bdev->bd_dev)); return ret; } EXPORT_SYMBOL(bdev_filter_attach); /* * Only for livepatch version * It is necessary for correct processing of the case when the block device * was removed from the system. Unlike the upstream version, we have no way * to handle device extension. */ int lp_bdev_filter_detach(const dev_t dev_id, const char *name, const enum bdev_filter_altitudes altitude) { struct bdev_extension *ext; struct bdev_filter *flt; pr_info("Detach block device filter '%s'", name); if ((altitude < 0) || (altitude >= bdev_filter_alt_end)) return -EINVAL; spin_lock(&bdev_extension_list_lock); ext = bdev_extension_find(dev_id); spin_unlock(&bdev_extension_list_lock); if (!ext) return -ENOENT; spin_lock(&ext->bd_filters_lock); flt = ext->bd_filters[altitude]; if (flt) ext->bd_filters[altitude] = NULL; spin_unlock(&ext->bd_filters_lock); if (!flt) return -ENOENT; bdev_filter_put(flt); pr_info("Block device filter '%s' has been detached from %d:%d", name, MAJOR(dev_id), MINOR(dev_id)); return 0; } EXPORT_SYMBOL(lp_bdev_filter_detach); /** * bdev_filter_detach - Detach a filter from the block device. * @bdev: * block device. * @name: * Name of the block device filter. * @altitude: * Number of the block device filter. * * The filter should be added using the bdev_filter_attach() function. * * Return: * 0 - OK * -ENOENT - the filter was not found in the linked list * -EINVAL - invalid altitude */ int bdev_filter_detach(struct block_device *bdev, const char *name, const enum bdev_filter_altitudes altitude) { return lp_bdev_filter_detach(bdev->bd_dev, name, altitude); } EXPORT_SYMBOL(bdev_filter_detach); /** * bdev_filter_get_by_altitude - Get filters context value. * @bdev: * Block device ID. * @altitude: * Number of the block device filter. * * Return pointer to &struct bdev_filter or NULL if the filter was not found. * * Necessary to lock list of filters by calling bdev_filter_read_lock(). */ struct bdev_filter *bdev_filter_get_by_altitude(struct block_device *bdev, const enum bdev_filter_altitudes altitude) { struct bdev_extension *ext; struct bdev_filter *flt = NULL; if ((altitude < 0) || (altitude >= bdev_filter_alt_end)) return ERR_PTR(-EINVAL); spin_lock(&bdev_extension_list_lock); #if defined(HAVE_BI_BDISK) ext = bdev_extension_find_part(bdev->bd_disk, bdev->bd_partno); #else ext = bdev_extension_find_bdev(bdev); #endif spin_unlock(&bdev_extension_list_lock); if (!ext) return NULL; spin_lock(&ext->bd_filters_lock); flt = ext->bd_filters[altitude]; if (flt) bdev_filter_get(flt); spin_unlock(&ext->bd_filters_lock); return flt; } EXPORT_SYMBOL(bdev_filter_get_by_altitude); static inline bool bdev_filters_apply(struct bio *bio) { enum bdev_filter_altitudes altitude = 0; struct bdev_extension *ext; spin_lock(&bdev_extension_list_lock); #if defined(HAVE_BI_BDISK) ext = bdev_extension_find_part(bio->bi_disk, bio->bi_partno); #else ext = bdev_extension_find_bdev(bio->bi_bdev); #endif spin_unlock(&bdev_extension_list_lock); if (!ext) return true; spin_lock(&ext->bd_filters_lock); while (altitude < bdev_filter_alt_end) { enum bdev_filter_result result; struct bdev_filter *flt; flt = ext->bd_filters[altitude]; if (!flt) { altitude++; continue; } bdev_filter_get(flt); spin_unlock(&ext->bd_filters_lock); result = flt->fops->submit_bio_cb(bio, flt); bdev_filter_put(flt); if (result != bdev_filter_res_pass) return false; altitude++; spin_lock(&ext->bd_filters_lock); }; spin_unlock(&ext->bd_filters_lock); return true; } /** * submit_bio_noacct_notrace() - Execute submit_bio_noacct() without handling. */ #if defined(HAVE_QC_SUBMIT_BIO_NOACCT) notrace blk_qc_t submit_bio_noacct_notrace(struct bio *bio) #elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) notrace void submit_bio_noacct_notrace(struct bio *bio) #else #error "Your kernel is too old for this module." #endif { #if defined(HAVE_QC_SUBMIT_BIO_NOACCT) return submit_bio_noacct(bio); #elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) submit_bio_noacct(bio); #endif } EXPORT_SYMBOL(submit_bio_noacct_notrace); static notrace __attribute__((optimize("no-optimize-sibling-calls"))) #if defined(HAVE_QC_SUBMIT_BIO_NOACCT) blk_qc_t submit_bio_noacct_handler(struct bio *bio) #elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) void submit_bio_noacct_handler(struct bio *bio) #endif { if (!bdev_filters_apply(bio)) { #if defined(HAVE_QC_SUBMIT_BIO_NOACCT) return BLK_QC_T_NONE; #elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) return; #endif } #if defined(HAVE_QC_SUBMIT_BIO_NOACCT) return submit_bio_noacct_notrace(bio); #elif defined(HAVE_VOID_SUBMIT_BIO_NOACCT) submit_bio_noacct_notrace(bio); #endif } static notrace void ftrace_handler_submit_bio_noacct( unsigned long ip, unsigned long parent_ip, struct ftrace_ops *fops, #ifdef HAVE_FTRACE_REGS struct ftrace_regs *fregs #else struct pt_regs *regs #endif ) { if (!current->bio_list && !within_module(parent_ip, THIS_MODULE)) { #if defined(HAVE_FTRACE_REGS_SET_INSTRUCTION_POINTER) ftrace_regs_set_instruction_pointer(fregs, (unsigned long)submit_bio_noacct_handler); #elif defined(HAVE_FTRACE_REGS) ftrace_instruction_pointer_set(fregs, (unsigned long)submit_bio_noacct_handler); #else instruction_pointer_set(regs, (unsigned long)submit_bio_noacct_handler); #endif } } static struct ftrace_ops ops_submit_bio_noacct = { .func = ftrace_handler_submit_bio_noacct, .flags = FTRACE_OPS_FL_DYNAMIC | FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_IPMODIFY | FTRACE_OPS_FL_PERMANENT, }; #ifdef HAVE_NOT_FTRACE_FREE_FILTER static unsigned long addr_ftrace_free_filter; #endif static int get_symbol(const char *name, void **paddr) { int ret; struct kprobe kp = {0}; kp.symbol_name = name; ret = register_kprobe(&kp); if (ret) { pr_err("Failed to get address of the '%s'\n", name); return ret; } *paddr = kp.addr; unregister_kprobe(&kp); return 0; } static int prepare_fn(void ) { int ret; unsigned long kernel_base; void *addr; ret = get_symbol("get_option", &addr); if (ret) return ret; kernel_base = (unsigned long)(get_option) - (unsigned long)addr; #ifdef HAVE_NOT_FTRACE_FREE_FILTER ret = get_symbol("ftrace_free_filter", &addr); if (ret) return ret; addr_ftrace_free_filter = kernel_base + (unsigned long)addr; #endif return 0; } static int __init bdevfilter_init(void) { int ret; ret = prepare_fn(); if (ret) { pr_err("Failed to prepare pointers to internal functions\n"); return ret; } ret = ftrace_set_filter(&ops_submit_bio_noacct, "submit_bio_noacct", strlen("submit_bio_noacct"), 0); if (ret) { pr_err("Failed to set ftrace handler for function 'submit_bio_noacct'\n"); return ret; } ret = register_ftrace_function(&ops_submit_bio_noacct); if (ret) { pr_err("Failed to register ftrace handler (%d)\n", ret); #ifdef HAVE_NOT_FTRACE_FREE_FILTER ((void (*)(struct ftrace_ops *ops))addr_ftrace_free_filter)(&ops_submit_bio_noacct); #else ftrace_free_filter(&ops_submit_bio_noacct); #endif return ret; } pr_debug("Ftrace filter for 'submit_bio_noacct' has been registered\n"); return 0; } static void __exit bdevfilter_done(void) { struct bdev_extension *ext; unregister_ftrace_function(&ops_submit_bio_noacct); #ifdef HAVE_NOT_FTRACE_FREE_FILTER ((void (*)(struct ftrace_ops *ops))addr_ftrace_free_filter)(&ops_submit_bio_noacct); #else ftrace_free_filter(&ops_submit_bio_noacct); #endif pr_debug("Ftrace filter for 'submit_bio_noacct' has been unregistered\n"); spin_lock(&bdev_extension_list_lock); while ((ext = list_first_entry_or_null(&bdev_extension_list, struct bdev_extension, link))) { list_del(&ext->link); kfree(ext); } spin_unlock(&bdev_extension_list_lock); } module_init(bdevfilter_init); module_exit(bdevfilter_done); MODULE_DESCRIPTION("Block Device Filter kernel module"); MODULE_VERSION(VERSION_STR); MODULE_AUTHOR("Veeam Software Group GmbH"); MODULE_LICENSE("GPL"); /* Allow to be loaded on OpenSUSE/SLES */ MODULE_INFO(supported, "external");