%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /var/lib/dkms/blksnap/6.3.0.73/source/
Upload File :
Create Path :
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");

Zerion Mini Shell 1.0