| Server IP : 64.223.137.62 / Your IP : 104.23.197.97 Web Server : Apache/2.4.52 (Ubuntu) System : Linux webserver9 5.15.0-170-generic #180-Ubuntu SMP Fri Jan 9 16:10:31 UTC 2026 x86_64 User : www-data ( 33) PHP Version : 8.1.2-1ubuntu2.24 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /usr/src/synosnap-0.12.10/ |
Upload File : |
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2015 Datto Inc.
* Additional contributions by Elastio Software, Inc are Copyright (C) 2020 Elastio Software Inc.
*/
#include "includes.h"
#include "kernel-config.h"
#include "elastio-snap.h"
#ifdef NETLINK_DEBUG
#include "nl_debug.h"
#endif
//current lowest supported kernel = 3.10.0
//basic information
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tom Caputi");
MODULE_DESCRIPTION("Kernel module for supporting block device snapshots and incremental backups.");
MODULE_VERSION(ELASTIO_SNAP_VERSION);
//printing macros
#define LOG_DEBUG(fmt, args...) \
do{ \
if(elastio_snap_debug) printk(KERN_DEBUG "synosnap: " fmt "\n", ## args); \
}while(0)
#define LOG_WARN(fmt, args...) printk(KERN_WARNING "synosnap: " fmt "\n", ## args)
#define LOG_ERROR(error, fmt, args...) printk(KERN_ERR "synosnap: " fmt ": %d\n", ## args, error)
#define PRINT_BIO(text, bio) LOG_DEBUG(text ": sect = %llu size = %u", (unsigned long long)bio_sector(bio), bio_size(bio) / 512)
/*********************************REDEFINED FUNCTIONS*******************************/
#include <linux/delay.h>
#include <linux/fiemap.h>
#include <linux/version.h>
#ifdef HAVE_UUID_H
#include <linux/uuid.h>
#endif
#ifdef HAVE_UAPI_MOUNT_H
#include <uapi/linux/mount.h>
#endif
#if defined HAVE_BLK_MQ_MAKE_REQUEST || defined HAVE_BLK_MQ_SUBMIT_BIO
#include <linux/blk-mq.h>
#include <linux/percpu-refcount.h>
#endif
#ifndef HAVE_BIO_LIST
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
struct bio_list {
struct bio *head;
struct bio *tail;
};
#define BIO_EMPTY_LIST { NULL, NULL }
#define bio_list_for_each(bio, bl) for((bio) = (bl)->head; (bio); (bio) = (bio)->bi_next)
static inline int bio_list_empty(const struct bio_list *bl){
return bl->head == NULL;
}
static inline void bio_list_init(struct bio_list *bl){
bl->head = bl->tail = NULL;
}
static inline void bio_list_add(struct bio_list *bl, struct bio *bio){
bio->bi_next = NULL;
if (bl->tail) bl->tail->bi_next = bio;
else bl->head = bio;
bl->tail = bio;
}
static inline struct bio *bio_list_pop(struct bio_list *bl){
struct bio *bio = bl->head;
if (bio) {
bl->head = bl->head->bi_next;
if (!bl->head) bl->tail = NULL;
bio->bi_next = NULL;
}
return bio;
}
#endif
#ifndef HAVE_D_UNLINKED
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
static inline int d_unlinked(struct dentry *dentry){
return d_unhashed(dentry) && !IS_ROOT(dentry);
}
#endif
#ifndef HAVE_NOOP_LLSEEK
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)
static loff_t noop_llseek(struct file *file, loff_t offset, int origin){
return file->f_pos;
}
#endif
#ifndef HAVE_STRUCT_PATH
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
struct path {
struct vfsmount *mnt;
struct dentry *dentry;
};
#define elastio_snap_get_dentry(f) (f)->f_dentry
#define elastio_snap_get_mnt(f) (f)->f_vfsmnt
#else
#define elastio_snap_get_dentry(f) (f)->f_path.dentry
#define elastio_snap_get_mnt(f) (f)->f_path.mnt
#endif
#ifdef HAVE_BDEVNAME
#define elastio_snap_bdevname(dev, buf) bdevname(dev, buf)
#else
#define elastio_snap_bdevname(dev, buf) snprintf(buf, sizeof(buf), "%pg", dev);
#endif
#ifndef HAVE_PATH_PUT
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
void path_put(const struct path *path) {
dput(path->dentry);
mntput(path->mnt);
}
#define elastio_snap_d_path(path, page_buf, page_size) d_path((path)->dentry, (path)->mnt, page_buf, page_size)
#define elastio_snap_get_nd_dentry(nd) (nd).dentry
#define elastio_snap_get_nd_mnt(nd) (nd).mnt
#else
#define elastio_snap_d_path(path, page_buf, page_size) d_path(path, page_buf, page_size)
#define elastio_snap_get_nd_dentry(nd) (nd).path.dentry
#define elastio_snap_get_nd_mnt(nd) (nd).path.mnt
#endif
#ifndef HAVE_FMODE_T
typedef mode_t fmode_t;
#endif
#ifndef HAVE_BLK_ALLOC_QUEUE_MK_REQ_FN_NODE_ID
struct request_queue* (*elastio_blk_alloc_queue)(int node_id) = (BLK_ALLOC_QUEUE_ADDR != 0) ?
(struct request_queue* (*)(int node_id)) (BLK_ALLOC_QUEUE_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
#endif
struct super_block* (*__elastio_snap_get_super)(struct block_device *) = (GET_SUPER_ADDR != 0) ?
(struct super_block* (*)(struct block_device*)) (GET_SUPER_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
struct super_block* (*__elastio_snap_user_get_super)(dev_t, bool) = (USER_GET_SUPER_ADDR != 0) ?
(struct super_block* (*)(dev_t, bool)) (USER_GET_SUPER_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
static struct super_block *elastio_snap_get_super(struct block_device *bdev)
{
#ifdef HAVE_GET_SUPER
return __elastio_snap_get_super(bdev);
#else
return __elastio_snap_user_get_super(bdev->bd_dev, false);
#endif
}
#if !(defined HAVE_BLKDEV_GET_BY_PATH || defined HAVE_BLKDEV_GET_BY_PATH_4 || \
defined HAVE_BDEV_OPEN_BY_PATH || defined HAVE_BDEV_FILE_OPEN_BY_PATH)
static struct block_device *elastio_snap_lookup_bdev(const char *pathname, fmode_t mode) {
int r;
struct block_device *retbd;
struct nameidata nd;
struct inode *inode;
dev_t dev;
if ((r = path_lookup(pathname, LOOKUP_FOLLOW, &nd)))
goto fail;
inode = elastio_snap_get_nd_dentry(nd)->d_inode;
if (!inode) {
r = -ENOENT;
goto fail;
}
if (!S_ISBLK(inode->i_mode)) {
r = -ENOTBLK;
goto fail;
}
dev = inode->i_rdev;
retbd = open_by_devnum(dev, mode);
out:
#ifdef HAVE_PATH_PUT
path_put(&nd.path);
#else
dput(nd.dentry);
mntput(nd.mnt);
#endif
return retbd;
fail:
retbd = ERR_PTR(r);
goto out;
}
#endif
#if !(defined HAVE_BLKDEV_GET_BY_PATH || defined HAVE_BLKDEV_GET_BY_PATH_4 || \
defined HAVE_BDEV_OPEN_BY_PATH || defined HAVE_BDEV_FILE_OPEN_BY_PATH)
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)
static struct block_device *blkdev_get_by_path(const char *path, fmode_t mode, void *holder){
struct block_device *bdev;
bdev = elastio_snap_lookup_bdev(path, mode);
if(IS_ERR(bdev))
return bdev;
if((mode & FMODE_WRITE) && bdev_read_only(bdev)) {
#ifdef HAVE_BLKDEV_PUT_1
blkdev_put(bdev);
#else
blkdev_put(bdev, mode);
#endif
return ERR_PTR(-EACCES);
}
return bdev;
}
#endif
struct bdev_container {
#if defined HAVE_BDEV_OPEN_BY_PATH
struct bdev_handle *bd_handle;
#elif defined HAVE_BDEV_FILE_OPEN_BY_PATH
struct file *bdev_file;
#else
struct block_device *bdev;
#endif
};
static struct block_device *elastio_snap_blkdev_get_by_path(struct bdev_container *bd_c, const char *path, fmode_t mode, void *holder)
{
if (!bd_c)
return ERR_PTR(-EINVAL);
#if defined HAVE_BDEV_OPEN_BY_PATH
bd_c->bd_handle = bdev_open_by_path(path, mode, holder, NULL);
if (IS_ERR(bd_c->bd_handle))
return ERR_PTR(PTR_ERR(bd_c->bd_handle));
return bd_c->bd_handle->bdev;
#elif defined HAVE_BDEV_FILE_OPEN_BY_PATH
bd_c->bdev_file = bdev_file_open_by_path(path, mode, holder, NULL);
if (IS_ERR(bd_c->bdev_file))
return ERR_PTR(PTR_ERR(bd_c->bdev_file));
return file_bdev(bd_c->bdev_file);
#elif defined HAVE_BLKDEV_GET_BY_PATH_4
bd_c->bdev = blkdev_get_by_path(path, mode, holder, NULL);
return bd_c->bdev;
#else
bd_c->bdev = blkdev_get_by_path(path, mode, holder);
return bd_c->bdev;
#endif
}
static int elastio_snap_blkdev_put(struct bdev_container *bd_c)
{
if (IS_ERR(bd_c)) {
LOG_ERROR(-EINVAL, "%s(): invalid argument", __func__);
return -EINVAL;
}
#if defined HAVE_BDEV_OPEN_BY_PATH
bdev_release(bd_c->bd_handle);
#elif defined HAVE_BDEV_FILE_OPEN_BY_PATH
fput(bd_c->bdev_file);
#elif defined HAVE_BLKDEV_PUT_1
blkdev_put(bd_c->bdev);
#elif defined HAVE_BLKDEV_PUT_HOLDER
blkdev_put(bd_c->bdev, NULL);
#else
blkdev_put(bd_c->bdev, FMODE_READ);
#endif
return 0;
}
static size_t elastio_strscpy(char *dst, const char *src, size_t sz)
{
#ifdef HAVE_STRSCPY
return strscpy(dst, src, sz);
#else
return strlcpy(dst, src, sz);
#endif
}
#ifndef HAVE_ALLOC_DISK
static struct gendisk *elastio_snap_blk_alloc_disk(void)
{
#if defined HAVE_BLK_ALLOC_DISK_2
return blk_alloc_disk(NULL, NUMA_NO_NODE);
#else
return blk_alloc_disk(NUMA_NO_NODE);
#endif
}
#endif
#ifndef REQ_WRITE
#define REQ_WRITE WRITE
#endif
#ifndef REQ_FLUSH
#define REQ_FLUSH (1 << BIO_RW_BARRIER)
#endif
//if these don't exist they are not supported
#ifndef REQ_SECURE
#define REQ_SECURE 0
#endif
#ifndef REQ_WRITE_SAME
#define REQ_WRITE_SAME 0
#endif
#ifndef HAVE_SUBMIT_BIO_1
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0)
#ifndef REQ_DISCARD
#define REQ_DISCARD 0
#endif
#ifndef HAVE_ENUM_REQ_OP
typedef enum req_op {
REQ_OP_READ,
REQ_OP_WRITE,
REQ_OP_DISCARD, /* request to discard sectors */
REQ_OP_SECURE_ERASE, /* request to securely erase sectors */
REQ_OP_WRITE_SAME, /* write same block many times */
REQ_OP_FLUSH, /* request for cache flush */
} req_op_t;
static inline void elastio_snap_set_bio_ops(struct bio *bio, req_op_t op, unsigned op_flags){
bio->bi_rw = 0;
switch(op){
case REQ_OP_READ:
break;
case REQ_OP_WRITE:
bio->bi_rw |= REQ_WRITE;
break;
case REQ_OP_DISCARD:
bio->bi_rw |= REQ_DISCARD;
break;
case REQ_OP_SECURE_ERASE:
bio->bi_rw |= REQ_DISCARD | REQ_SECURE;
break;
case REQ_OP_WRITE_SAME:
bio->bi_rw |= REQ_WRITE_SAME;
break;
case REQ_OP_FLUSH:
bio->bi_rw |= REQ_FLUSH;
break;
}
bio->bi_rw |= op_flags;
}
#else
typedef enum req_op req_op_t;
#define elastio_snap_set_bio_ops(bio, op, flags) bio_set_op_attrs(bio, op, flags)
#endif
#define bio_is_discard(bio) ((bio)->bi_rw & REQ_DISCARD)
#define elastio_snap_submit_bio(bio) submit_bio(0, bio)
#define elastio_snap_submit_bio_wait(bio) submit_bio_wait(0, bio)
static inline int elastio_snap_bio_op_flagged(struct bio *bio, unsigned int flag){
return bio->bi_rw & flag;
}
static inline void elastio_snap_bio_op_set_flag(struct bio *bio, unsigned int flag){
bio->bi_rw |= flag;
}
static inline void elastio_snap_bio_op_clear_flag(struct bio *bio, unsigned int flag){
bio->bi_rw &= ~flag;
}
#else
#ifndef HAVE_ENUM_REQ_OPF
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
typedef enum req_op req_op_t;
#else
typedef enum req_opf req_op_t;
#endif
static inline void elastio_snap_set_bio_ops(struct bio *bio, req_op_t op, unsigned op_flags){
#ifdef HAVE_BIO_SET_OP_ATTRS
bio->bi_opf = 0;
bio_set_op_attrs(bio, op, op_flags);
#else
bio->bi_opf = op | op_flags;
#endif
}
static inline int elastio_snap_bio_op_flagged(struct bio *bio, unsigned int flag){
return bio->bi_opf & flag;
}
static inline void elastio_snap_bio_op_set_flag(struct bio *bio, unsigned int flag){
bio->bi_opf |= flag;
}
static inline void elastio_snap_bio_op_clear_flag(struct bio *bio, unsigned int flag){
bio->bi_opf &= ~flag;
}
#ifdef REQ_DISCARD
#define bio_is_discard(bio) ((bio)->bi_opf & REQ_DISCARD)
#else
#define bio_is_discard(bio) (bio_op(bio) == REQ_OP_DISCARD || bio_op(bio) == REQ_OP_SECURE_ERASE)
#endif
#define elastio_snap_submit_bio(bio) submit_bio(bio)
#define elastio_snap_submit_bio_wait(bio) submit_bio_wait(bio)
#endif
#if !defined HAVE_SUBMIT_BIO_WAIT && !defined HAVE_SUBMIT_BIO_1
//#if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)
struct submit_bio_ret{
struct completion event;
int error;
};
static void __submit_bio_wait_endio(struct bio *bio, int error){
struct submit_bio_ret *ret = bio->bi_private;
ret->error = error;
complete(&ret->event);
}
#ifdef HAVE_BIO_ENDIO_INT
static int submit_bio_wait_endio(struct bio *bio, unsigned int bytes, int error){
if (bio->bi_size) return 1;
__submit_bio_wait_endio(bio, error);
return 0;
}
#else
static void submit_bio_wait_endio(struct bio *bio, int error){
__submit_bio_wait_endio(bio, error);
}
#endif
static int submit_bio_wait(int rw, struct bio *bio){
struct submit_bio_ret ret;
//kernel implementation has the line below, but all our calls will have this already and it changes across kernel versions
//rw |= REQ_SYNC;
init_completion(&ret.event);
bio->bi_private = &ret;
bio->bi_end_io = submit_bio_wait_endio;
submit_bio(rw, bio);
wait_for_completion(&ret.event);
return ret.error;
}
#endif
#ifdef HAVE_BIO_ENDIO_INT
static void elastio_snap_bio_endio(struct bio *bio, int err){
bio_endio(bio, bio->bi_size, err);
}
#elif !defined HAVE_BIO_ENDIO_1
static void elastio_snap_bio_endio(struct bio *bio, int err){
bio_endio(bio, err);
}
#elif defined HAVE_BLK_STATUS_T
static void elastio_snap_bio_endio(struct bio *bio, int err){
bio->bi_status = errno_to_blk_status(err);
bio_endio(bio);
}
#else
static void elastio_snap_bio_endio(struct bio *bio, int err){
bio->bi_error = err;
bio_endio(bio);
}
#endif
//the kernel changed the usage of bio_for_each_segment in 3.14. Do not use any fields directly or you will lose compatibility.
#ifndef HAVE_BVEC_ITER
//#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
typedef int bio_iter_t;
typedef struct bio_vec *bio_iter_bvec_t;
#define bio_iter_len(bio, iter) ((bio)->bi_io_vec[(iter)].bv_len)
#define bio_iter_offset(bio, iter) ((bio)->bi_io_vec[(iter)].bv_offset)
#define bio_iter_page(bio, iter) ((bio)->bi_io_vec[(iter)].bv_page)
#define bio_iter_idx(iter) (iter)
#define bio_sector(bio) (bio)->bi_sector
#define bio_size(bio) (bio)->bi_size
#define bio_idx(bio) (bio)->bi_idx
#else
typedef struct bvec_iter bio_iter_t;
typedef struct bio_vec bio_iter_bvec_t;
#define bio_iter_idx(iter) ((iter).bi_idx)
#define bio_sector(bio) (bio)->bi_iter.bi_sector
#define bio_size(bio) (bio)->bi_iter.bi_size
#define bio_idx(bio) (bio)->bi_iter.bi_idx
#endif
#ifndef HAVE_MNT_WANT_WRITE
#define mnt_want_write(x) 0
#define mnt_drop_write (void)sizeof
#endif
#ifndef UMOUNT_NOFOLLOW
#define UMOUNT_NOFOLLOW 0
#endif
#if !defined(HAVE_BDEV_STACK_LIMITS)
//#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32)
static int bdev_stack_limits(struct queue_limits *t, struct block_device *bdev, sector_t start){
struct request_queue *bq = bdev_get_queue(bdev);
start += get_start_sect(bdev);
return blk_stack_limits(t, &bq->limits, start << 9);
}
#define elastio_snap_bdev_stack_limits(queue, bdev, start) bdev_stack_limits(&(queue)->limits, bdev, start)
#else
#define elastio_snap_bdev_stack_limits(queue, bdev, start) bdev_stack_limits(&(queue)->limits, bdev, start)
#endif
#ifndef HAVE_KERN_PATH
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
static int kern_path(const char *name, unsigned int flags, struct path *path){
struct nameidata nd;
int ret = path_lookup(name, flags, &nd);
if(!ret){
path->dentry = elastio_snap_get_nd_dentry(nd);
path->mnt = elastio_snap_get_nd_mnt(nd);
}
return ret;
}
#endif
#ifdef HAVE_BIOSET_NEED_BVECS_FLAG
#define elastio_snap_bioset_create(bio_size, bvec_size, scale) bioset_create(bio_size, bvec_size, BIOSET_NEED_BVECS)
#elif defined HAVE_BIOSET_CREATE_3
#define elastio_snap_bioset_create(bio_size, bvec_size, scale) bioset_create(bio_size, bvec_size, scale)
#else
#define elastio_snap_bioset_create(bio_size, bvec_size, scale) bioset_create(bio_size, scale)
#endif
#ifndef HAVE_USER_PATH_AT
int user_path_at(int dfd, const char __user *name, unsigned flags, struct path *path) {
struct nameidata nd;
char *tmp = getname(name);
int err = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
BUG_ON(flags & LOOKUP_PARENT);
err = path_lookup(tmp, flags, &nd);
putname(tmp);
if (!err) {
path->dentry = elastio_snap_get_nd_dentry(nd);
path->mnt = elastio_snap_get_nd_mnt(nd);
}
}
return err;
}
#endif
static int elastio_snap_should_remove_suid(struct dentry *dentry)
{
mode_t mode = dentry->d_inode->i_mode;
int kill = 0;
/* suid always must be killed */
if (unlikely(mode & S_ISUID))
kill = ATTR_KILL_SUID;
/*
* sgid without any exec bits is just a mandatory locking mark; leave
* it alone. If some exec bits are set, it's a real sgid; kill it.
*/
if (unlikely((mode & S_ISGID) && (mode & S_IXGRP)))
kill |= ATTR_KILL_SGID;
if (unlikely(kill && !capable(CAP_FSETID) && S_ISREG(mode)))
return kill;
return 0;
}
#ifdef HAVE_BIO_BI_BDEV_BD_DISK
#define elastio_snap_bio_bi_disk(bio) ((bio)->bi_bdev->bd_disk)
#else
//#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0)
#define elastio_snap_bio_bi_disk(bio) ((bio)->bi_disk)
#endif
#ifdef HAVE_BDEV_NR_SECTORS
#define elastio_snap_bdev_size(bdev) bdev_nr_sectors(bdev)
#elif defined HAVE_PART_NR_SECTS_READ
//#if LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
#define elastio_snap_bdev_size(bdev) part_nr_sects_read((bdev)->bd_part)
#else
//#if LINUX_VERSION_CODE < KERNEL_VERSION(3,6,0)
#define elastio_snap_bdev_size(bdev) ((bdev)->bd_part->nr_sects)
#endif
#ifndef HAVE_BDEV_IS_PARTITION
//#if LINUX_VERSION_CODE < KERNEL_VERSION(5,10,0)
#define elastio_snap_bdev_is_partition(bdev) (bdev->bd_contains != bdev)
#else
#define elastio_snap_bdev_is_partition(bdev) bdev_is_partition(bdev)
#endif
#ifndef HAVE_VZALLOC
#define vzalloc(size) __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO, PAGE_KERNEL)
#endif
#if !defined HAVE_MAKE_REQUEST_FN_IN_QUEUE && defined HAVE_BDOPS_SUBMIT_BIO_UINT
// Linux kernel version 5.9 - 5.15
// make_request_fn has been moved from the request queue structure to the
// block_device_operations as submit_bio function with UINT return type.
// See https://github.com/torvalds/linux/commit/c62b37d96b6eb3ec5ae4cbe00db107bf15aebc93
#define USE_BDOPS_SUBMIT_BIO
// Prototype bdev->fops->submit_bio but with the name already used in the code
typedef blk_qc_t (make_request_fn) (struct bio *bio);
#endif
#if !defined HAVE_MAKE_REQUEST_FN_IN_QUEUE && defined HAVE_BDOPS_SUBMIT_BIO
// Linux kernel version 5.16+
// submit_bio function in the block_device_operations structure has changed its return type to VOID.
// See https://github.com/torvalds/linux/commit/3e08773c3841e9db7a520908cc2b136a77d275ff#diff79b436371fdb3ddf0e7ad9bd4c9afe05160f7953438e650a77519b882904c56bR1181
#define USE_BDOPS_SUBMIT_BIO
// Prototype bdev->fops->submit_bio but with the name already used in the code
typedef void (make_request_fn) (struct bio *bio);
#endif
#ifndef USE_BDOPS_SUBMIT_BIO
static inline make_request_fn* elastio_snap_get_bd_mrf(struct block_device *bdev){
return bdev->bd_disk->queue->make_request_fn;
}
static inline void elastio_snap_set_bd_mrf(struct block_device *bdev, make_request_fn *mrf){
bdev->bd_disk->queue->make_request_fn = mrf;
}
#else
static inline struct block_device_operations* elastio_snap_get_bd_ops(struct block_device *bdev){
return (struct block_device_operations*)bdev->bd_disk->fops;
}
static inline void elastio_snap_set_bd_ops(struct block_device *bdev, const struct block_device_operations *bd_ops){
bdev->bd_disk->fops = bd_ops;
}
static inline make_request_fn* elastio_snap_get_bd_mrf(struct block_device *bdev){
return bdev->bd_disk->fops->submit_bio;
}
#endif
static inline struct request_queue *elastio_snap_bio_get_queue(struct bio *bio);
#ifdef HAVE_MAKE_REQUEST_FN_INT
#define MRF_RETURN_TYPE int
#define MRF_RETURN(ret) return ret
static inline int __elastio_snap_call_mrf(make_request_fn *fn, struct request_queue *q, struct bio *bio){
return fn(q, bio);
}
static inline int elastio_snap_call_mrf(make_request_fn *fn, struct bio *bio){
return __elastio_snap_call_mrf(fn, elastio_snap_bio_get_queue(bio), bio);
}
#elif defined HAVE_MAKE_REQUEST_FN_VOID
#define MRF_RETURN_TYPE void
#define MRF_RETURN(ret) return
#define MRF_RETURN_TYPE_VOID
static inline int __elastio_snap_call_mrf(make_request_fn *fn, struct request_queue *q, struct bio *bio){
fn(q, bio);
return 0;
}
static inline int elastio_snap_call_mrf(make_request_fn *fn, struct bio *bio){
__elastio_snap_call_mrf(fn, elastio_snap_bio_get_queue(bio), bio);
return 0;
}
#else
#ifdef HAVE_BDOPS_SUBMIT_BIO
// Linux kernel version 5.16+
#define MRF_RETURN_TYPE void
#define MRF_RETURN(ret) return
#define MRF_RETURN_TYPE_VOID
#else
// Linux kernel version 5.9 - 5.15
#define MRF_RETURN_TYPE blk_qc_t
#define MRF_RETURN(ret) return BLK_QC_T_NONE
#endif
#ifndef USE_BDOPS_SUBMIT_BIO
static inline int __elastio_snap_call_mrf(make_request_fn *fn, struct request_queue *q, struct bio *bio){
return fn(q, bio);
}
static inline int elastio_snap_call_mrf(make_request_fn *fn, struct bio *bio){
return __elastio_snap_call_mrf(fn, elastio_snap_bio_get_queue(bio), bio);
}
#endif
#ifdef HAVE_BLK_MQ_MAKE_REQUEST
// Linux version 5.8
static inline MRF_RETURN_TYPE elastio_snap_null_mrf(struct request_queue *q, struct bio *bio){
percpu_ref_get(&q->q_usage_counter);
return blk_mq_make_request(q, bio);
}
#endif
#endif
#ifdef MRF_RETURN_TYPE_VOID
#define MRF_SET_RETURN_VALUE(mrf_func) mrf_func
#define MRF_RETURN_VALUE(mrf_func) mrf_func
#else
#define MRF_SET_RETURN_VALUE(mrf_func) ret = mrf_func
#define MRF_RETURN_VALUE(mrf_func) return mrf_func
#endif
#ifdef USE_BDOPS_SUBMIT_BIO
// Linux version 5.9+
// The blk_mq_submit_bio function was exported in the kernels 5.9.0 - 5.9.1. And starting from the 5.9.2 it doesn't.
// And compat HAVE_BLK_MQ_SUBMIT_BIO doesn't allow us to detect whether it exported or not.
// Anyway this call by address works in all cases for the kernels 5.9+.
// Also elastio_blk_mq_submit_bio is set to NULL in case if address of the blk_mq_submit_bio function is not detected for further checks.
MRF_RETURN_TYPE (*elastio_blk_mq_submit_bio)(struct bio *) = (BLK_MQ_SUBMIT_BIO_ADDR != 0) ?
(MRF_RETURN_TYPE (*)(struct bio *)) (BLK_MQ_SUBMIT_BIO_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
static inline MRF_RETURN_TYPE elastio_snap_null_mrf(struct bio *bio){
#ifndef MRF_RETURN_TYPE_VOID
percpu_ref_get(&elastio_snap_bio_bi_disk(bio)->queue->q_usage_counter);
#endif
MRF_RETURN_VALUE(elastio_blk_mq_submit_bio(bio));
}
static inline int elastio_snap_call_mrf(make_request_fn *fn, struct bio *bio){
int ret = 0;
MRF_SET_RETURN_VALUE(fn(bio));
return ret;
}
#endif
#ifndef ACCESS_ONCE
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
#endif
//this is defined in 3.16 and up
#ifndef MIN_NICE
#define MIN_NICE -20
#endif
//if this isn't defined, we don't need it anyway
#ifndef FMODE_NONOTIFY
#define FMODE_NONOTIFY 0
#endif
#ifndef HAVE_BLK_SET_STACKING_LIMITS
#define blk_set_stacking_limits(ql) blk_set_default_limits(ql)
#endif
#ifndef HAVE_INODE_LOCK
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,5,0)
static inline void elastio_snap_inode_lock(struct inode *inode){
mutex_lock(&inode->i_mutex);
}
static inline void elastio_snap_inode_unlock(struct inode *inode){
mutex_unlock(&inode->i_mutex);
}
#else
#define elastio_snap_inode_lock inode_lock
#define elastio_snap_inode_unlock inode_unlock
#endif
#if !defined HAVE_PROC_CREATE_FN_FILE_OPERATIONS && !defined HAVE_PROC_CREATE_FN_PROC_OPS
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
static inline struct proc_dir_entry *proc_create(const char *name, mode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops){
struct proc_dir_entry *ent;
ent = create_proc_entry(name, mode, parent);
if(!ent) goto error;
ent->proc_fops = proc_fops;
return ent;
error:
return NULL;
}
#endif
static inline struct request_queue *elastio_snap_bio_get_queue(struct bio *bio){
#if defined HAVE_BIO_BI_BDEV && defined HAVE_MAKE_REQUEST_FN_IN_QUEUE
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
return bdev_get_queue(bio->bi_bdev);
#else
return elastio_snap_bio_bi_disk(bio)->queue;
#endif
}
static inline void elastio_snap_bio_set_dev(struct bio *bio, struct block_device *bdev){
#if defined HAVE_BIO_SET_DEV
bio_set_dev(bio, bdev);
#else
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
bio->bi_bdev = bdev;
#endif
}
static inline void elastio_snap_bio_copy_dev(struct bio *dst, struct bio *src){
#if defined HAVE_BIO_COPY_DEV
bio_copy_dev(dst, src);
#else
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
dst->bi_bdev = src->bi_bdev;
#endif
}
#ifndef HAVE_BIOSET_INIT
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,18,0)
#define dev_bioset(dev) ((dev)->sd_bioset)
#else
#define dev_bioset(dev) (&(dev)->sd_bioset)
#endif
#ifndef __kernel_long_t
typedef long __kernel_long_t;
typedef unsigned long __kernel_ulong_t;
#endif
#ifndef HAVE_SI_MEM_AVAILABLE
__kernel_ulong_t si_mem_available(void)
{
struct sysinfo si;
si_meminfo(&si);
return si.freeram;
}
#endif
#ifndef HAVE_BIO_FREE_PAGES
static void bio_free_pages(struct bio *bio){
struct page *bv_page;
#ifdef HAVE_BVEC_ITER_ALL
struct bvec_iter_all iter;
struct bio_vec *bvec;
bio_for_each_segment_all(bvec, bio, iter) {
#else
int i = 0;
struct bio_vec *bvec;
bio_for_each_segment_all(bvec, bio, i) {
#endif
bv_page = bvec->bv_page;
if (bv_page) {
__free_page(bv_page);
}
}
}
#endif
/*********************************MACRO/PARAMETER DEFINITIONS*******************************/
//memory macros
#define get_zeroed_pages(flags, order) __get_free_pages(((flags) | __GFP_ZERO), order)
//takes a value and the log of the value it should be rounded up to
#define NUM_SEGMENTS(x, log_size) (((x) + (1<<(log_size)) - 1) >> (log_size))
#define ROUND_UP(x, chunk) ((((x) + (chunk) - 1) / (chunk)) * (chunk))
#define ROUND_DOWN(x, chunk) (((x) / (chunk)) * (chunk))
//bitmap macros
#define bitmap_is_marked(bitmap, pos) (((bitmap)[(pos) / 8] & (1 << ((pos) % 8))) != 0)
#define bitmap_mark(bitmap, pos) (bitmap)[(pos) / 8] |= (1 << ((pos) % 8))
//name macros
// #define INFO_PROC_FILE "elastio-snap-info"
// #define DRIVER_NAME "elastio-snap"
// #define CONTROL_DEVICE_NAME "elastio-snap-ctl"
// #define SNAP_DEVICE_NAME "elastio-snap%d"
// #define SNAP_COW_THREAD_NAME_FMT "elastio_snap_cow%d"
// #define SNAP_MRF_THREAD_NAME_FMT "elastio_snap_mrf%d"
// #define INC_THREAD_NAME_FMT "elastio_snap_inc%d"
#define INFO_PROC_FILE "synosnap-info"
#define DRIVER_NAME "synosnap"
#define CONTROL_DEVICE_NAME "synosnap-ctl"
#define SNAP_DEVICE_NAME "synosnap%u"
#define SNAP_COW_THREAD_NAME_FMT "syno_snap_cow%d"
#define SNAP_MRF_THREAD_NAME_FMT "syno_snap_mrf%d"
#define INC_THREAD_NAME_FMT "syno_inc%d"
//macro for iterating over snap_devices (requires a null check on dev)
#define tracer_for_each(dev, i) for(i = ACCESS_ONCE(lowest_minor), dev = ACCESS_ONCE(snap_devices[i]); i <= ACCESS_ONCE(highest_minor); i++, dev = ACCESS_ONCE(snap_devices[i]))
#define tracer_for_each_full(dev, i) for(i = 0, dev = ACCESS_ONCE(snap_devices[i]); i < elastio_snap_max_snap_devices; i++, dev = ACCESS_ONCE(snap_devices[i]))
#ifdef USE_BDOPS_SUBMIT_BIO
//returns true if tracing struct's base device fops matches that of bio
#define tracer_matches_bio(dev, bio) (elastio_snap_get_bd_ops(dev->sd_base_dev) == elastio_snap_bio_bi_disk(bio)->fops)
#else
//returns true if tracing struct's base device queue matches that of bio
#define tracer_matches_bio(dev, bio) (bdev_get_queue((dev)->sd_base_dev) == elastio_snap_bio_get_queue(bio))
#endif
//returns true if tracing struct's sector range matches the sector of the bio
#define tracer_sector_matches_bio(dev, bio) (bio_sector(bio) >= (dev)->sd_sect_off && bio_sector(bio) < (dev)->sd_sect_off + (dev)->sd_size)
//should be called along with tracer_matches_bio to be valid. returns true if bio is a write, has a size,
//tracing struct is in non-fail state, and the device's sector range matches the bio
#define tracer_should_trace_bio(dev, bio) (bio_data_dir(bio) && bio_size(bio) && !tracer_read_fail_state(dev) && tracer_sector_matches_bio(dev, bio))
//macros for snapshot bio modes of operation
#define READ_MODE_COW_FILE 1
#define READ_MODE_BASE_DEVICE 2
#define READ_MODE_MIXED 3
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0)
#ifndef SECTOR_SHIFT
#define SECTOR_SHIFT 9
#endif
#ifndef SECTOR_SIZE
#define SECTOR_SIZE (1 << SECTOR_SHIFT)
#endif
//macros for defining sector and block sizes
#define SECTORS_PER_PAGE (PAGE_SIZE / SECTOR_SIZE)
#define COW_SECTION_SIZE PAGE_SIZE
#define SECTORS_PER_BLOCK (COW_BLOCK_SIZE / SECTOR_SIZE)
#define SECTOR_TO_BLOCK(sect) ((sect) / SECTORS_PER_BLOCK)
#define BLOCK_TO_SECTOR(block) ((block) * SECTORS_PER_BLOCK)
//macros for compilation
#define MAYBE_UNUSED(x) (void)(x)
//macros for defining the state of a tracing struct (bit offsets)
#define SNAPSHOT 0
#define ACTIVE 1
#define UNVERIFIED 2
#define TRACED 3
//macro for defining the cow state, whether it placed on bdev or not
#define COW_ON_BDEV 1
#define LOW_MEMORY_FAIL_PERCENT 5
//macros for working with bios
#define BIO_SET_SIZE 256
#define bio_last_sector(bio) (bio_sector(bio) + (bio_size(bio) / SECTOR_SIZE))
/* don't perform COW operation */
#if defined HAVE_ENUM_REQ_OP && defined REQ_OP_BITS
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,8,0) && LINUX_VERSION_CODE < KERNEL_VERSION(4,10,0)
/* special case for deb9's 4.9 train
* Bit 30 conflicts with struct bio's bi_opf opcode bitfield, which occupies the top 3 bits of the member. If we set
* that bit, it will mutate the operation that the bio is representing. Setting this to 28 puts this in an unused flag
* for bi_opf (that flag means something in struct request's cmd_flags, but we're not setting that).
*
* Note: CentOS 7 has enum req_op starting from the version 7.4, kernel 3.10.0-693. But this enum has just 4 values
* instead of 6 as in other kernels, where this enum is present. And it doesn't have defined REQ_OP_BITS, which could
* be defined and equal to the 2 bits.
*/
#define __ELASTIO_SNAP_PASSTHROUGH 28 // set as the last flag bit
#else
// set as an unused flag in versions older than 4.8
// set as an unused opcode bit in kernels newer than 4.9
#define __ELASTIO_SNAP_PASSTHROUGH 30
#endif
#define ELASTIO_SNAP_PASSTHROUGH (1ULL << __ELASTIO_SNAP_PASSTHROUGH)
#define ELASTIO_SNAP_DEFAULT_SNAP_DEVICES 24
#define ELASTIO_SNAP_MAX_SNAP_DEVICES 255
#if !defined BIO_MAX_PAGES && defined BIO_MAX_VECS
#define BIO_MAX_PAGES BIO_MAX_VECS
#endif
//global module parameters
static int elastio_snap_may_hook_syscalls = 1;
static unsigned long elastio_snap_cow_ext_buf_size = sizeof(struct fiemap_extent) * 1024;
static unsigned long elastio_snap_cow_max_memory_default = (300 * 1024 * 1024);
static unsigned int elastio_snap_cow_fallocate_percentage_default = 10;
static unsigned int elastio_snap_max_snap_devices = ELASTIO_SNAP_DEFAULT_SNAP_DEVICES;
static int elastio_snap_debug = 0;
module_param_named(may_hook_syscalls, elastio_snap_may_hook_syscalls, int, S_IRUGO);
MODULE_PARM_DESC(may_hook_syscalls, "if true, allows the kernel module to find and alter the system call table to allow tracing to work across remounts");
module_param_named(cow_ext_buf_size, elastio_snap_cow_ext_buf_size, ulong, 0);
MODULE_PARM_DESC(cow_ext_buf_size, "length of the cow file extension buffer (in bytes)");
module_param_named(cow_max_memory_default, elastio_snap_cow_max_memory_default, ulong, 0);
MODULE_PARM_DESC(cow_max_memory_default, "default maximum cache size (in bytes)");
module_param_named(cow_fallocate_percentage_default, elastio_snap_cow_fallocate_percentage_default, uint, 0);
MODULE_PARM_DESC(cow_fallocate_percentage_default, "default space allocated to the cow file (as integer percentage)");
module_param_named(max_snap_devices, elastio_snap_max_snap_devices, uint, S_IRUGO);
MODULE_PARM_DESC(max_snap_devices, "maximum number of tracers available");
module_param_named(debug, elastio_snap_debug, int, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "enables debug logging");
static int param_set_bio_stats(const char *buffer, const struct kernel_param *kp);
static const struct kernel_param_ops param_ops_bio_stats = {
.set = param_set_bio_stats,
.get = param_get_uint,
};
static int bio_stats;
module_param_cb(bio_show_stats, ¶m_ops_bio_stats, &bio_stats, 0644);
/*********************************STRUCT DEFINITIONS*******************************/
struct sector_set{
struct sector_set *next;
sector_t sect;
unsigned int len;
};
struct sset_list{
struct sector_set *head;
struct sector_set *tail;
};
struct bio_queue{
struct bio_list bios;
spinlock_t lock;
wait_queue_head_t event;
};
struct sset_queue{
struct sset_list ssets;
spinlock_t lock;
wait_queue_head_t event;
};
struct bio_sector_map{
struct bio *bio;
sector_t sect;
unsigned int size;
struct bio_sector_map *next;
};
struct bsector_list {
struct bio_sector_map* head;
struct bio_sector_map* tail;
};
struct tracing_params{
struct bio *orig_bio;
struct snap_device *dev;
atomic_t refs;
struct bsector_list bio_sects;
};
#ifdef USE_BDOPS_SUBMIT_BIO
struct tracing_ops {
struct block_device_operations *bd_ops;
#if defined HAVE_BD_HAS_SUBMIT_BIO || defined HAVE_BD_HAS_SUBMIT_BIO_FLAGS
bool has_submit_bio; // kernel version >= 6.4
#endif
atomic_t refs;
};
#endif
#ifdef REQ_OP_LAST
#define BIO_STATS_MAX_ELEMENTS REQ_OP_LAST
#else
// NOTE: HT: last 8 bits are used for operation, different distros have different implementations for it
#define BIO_STATS_MAX_ELEMENTS 256
#endif
struct cow_section{
char has_data; //zero if this section has mappings (on file or in memory)
unsigned long usage; //counter that keeps track of how often this section is used
uint64_t *mappings; //array of block addresses
};
struct cow_manager{
struct file *filp; //the file the cow manager is writing to
uint32_t flags; //flags representing current state of cow manager
uint64_t curr_pos; //current write head position
uint64_t data_offset; //starting offset of data
uint64_t file_max; //max size of the file before an error is thrown
uint64_t seqid; //sequence id, increments on each transition to snapshot mode
uint64_t version; //version of cow file format
uint64_t nr_changed_blocks; //number of changed blocks since last snapshot
uint8_t uuid[COW_UUID_SIZE]; //uuid for this series of snaphots
unsigned int log_sect_pages; //log2 of the number of pages needed to store a section
unsigned long sect_size; //size of a section in number of elements it can contain
unsigned long allocated_sects; //number of currently allocated sections
unsigned long total_sects; //total sections the cm log represents
unsigned long allowed_sects; //the maximum number of sections that may be allocated at once
struct cow_section *sects; //pointer to the array of sections of mappings
struct snap_device *dev;
};
struct snap_device{
unsigned int sd_minor; //minor number of the snapshot
unsigned long sd_state; //current state of the snapshot
bool sd_ignore_snap_errors; //whether or not to return EIO on read snap BIOs when an active snap in a failed state
//it's useful to avoid SIGBUS when the snapshot device is used as memory mapped file
unsigned long sd_cow_state; //current state of cow file
unsigned long sd_falloc_size; //space allocated to the cow file (in megabytes)
unsigned long sd_cache_size; //maximum cache size (in bytes)
unsigned long sd_bio_stats_total[BIO_STATS_MAX_ELEMENTS]; // histogram of all bio requests traced
unsigned long sd_bio_stats_traced[BIO_STATS_MAX_ELEMENTS]; // histogram of processed bio requests
atomic_t sd_refs; //number of users who have this device open
atomic_t sd_fail_code; //failure return code
atomic_t sd_ignore_requests; //if snap_mrf should ignore new bio requests
sector_t sd_sect_off; //starting sector of base block device
sector_t sd_size; //size of device in sectors
struct request_queue *sd_queue; //snap device request queue
struct gendisk *sd_gd; //snap device gendisk
struct block_device *sd_base_dev; //device being snapshot
struct bdev_container sd_bdev_container;
char *sd_bdev_path; //base device file path
struct cow_manager *sd_cow; //cow manager
char *sd_cow_path; //cow file path
struct inode *sd_cow_inode; //cow file inode
struct fiemap_extent *sd_cow_extents; //cow file extents
unsigned int sd_cow_ext_cnt; //cow file extents count
#ifdef USE_BDOPS_SUBMIT_BIO
struct block_device_operations *sd_orig_ops; //block device's original operations sructure with the submit bio function
struct tracing_ops *sd_tracing_ops; //block device's operations sructure, copy of the original one,
//but with the submit bio function used for tracing,
//wrapped into tracing_ops structure with the ref counter.
#endif
make_request_fn *sd_orig_mrf; //block device's original make request function
struct task_struct *sd_cow_thread; //thread for handling file read/writes
struct bio_queue sd_cow_bios; //list of outstanding cow bios
struct task_struct *sd_mrf_thread; //thread for handling file read/writes
struct bio_queue sd_orig_bios; //list of outstanding original bios
struct sset_queue sd_pending_ssets; //list of outstanding sector sets
#ifndef HAVE_BIOSET_INIT
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,18,0)
struct bio_set *sd_bioset; //allocation pool for bios
#else
struct bio_set sd_bioset; //allocation pool for bios
#endif
atomic64_t sd_submitted_cnt; //count of read clones submitted to underlying driver
atomic64_t sd_received_cnt; //count of read clones submitted to underlying driver
atomic64_t sd_processed_cnt; //count of read clones processed in snap_cow_thread()
atomic64_t sd_discard_dropped_cnt; //count of dropped discard bio requests
atomic64_t sd_discard_ignored_size; //total size of ignored discard sectors
};
static long ctrl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
#define COW_MAX_SIZE ULONG_MAX
#define snap_is_cancelled(cm) (cm->curr_pos == COW_MAX_SIZE)
#define snap_cancel(cm) (cm->curr_pos = COW_MAX_SIZE)
#ifdef HAVE_BDOPS_OPEN_INODE
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
static int snap_open(struct inode *inode, struct file *filp);
static int snap_release(struct inode *inode, struct file *filp);
#elif defined HAVE_BDOPS_OPEN_INT
//#elif LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)
static int snap_open(struct block_device *bdev, fmode_t mode);
static int snap_release(struct gendisk *gd, fmode_t mode);
#elif defined HAVE_BDOPS_OPEN_GENDISK
static int snap_open(struct gendisk *gd, fmode_t mode);
static void snap_release(struct gendisk *gd);
#else
static int snap_open(struct block_device *bdev, fmode_t mode);
static void snap_release(struct gendisk *gd, fmode_t mode);
#endif
#ifdef HAVE_BIO_ENDIO_INT
static int on_bio_read_complete(struct bio *bio, unsigned int bytes, int error);
#elif !defined HAVE_BIO_ENDIO_1
static void on_bio_read_complete(struct bio *bio, int err);
#else
static void on_bio_read_complete(struct bio *bio);
#endif
static int elastio_snap_proc_show(struct seq_file *m, void *v);
static void *elastio_snap_proc_start(struct seq_file *m, loff_t *pos);
static void *elastio_snap_proc_next(struct seq_file *m, void *v, loff_t *pos);
static void elastio_snap_proc_stop(struct seq_file *m, void *v);
static int elastio_snap_proc_open(struct inode *inode, struct file *filp);
static int elastio_snap_proc_release(struct inode *inode, struct file *file);
#define WAIT_SUBMITTED_BIOS_MSEC 5000
// wait msec value to be at least 100 msec as wait loop uses it by msleep of (100) timeout pieces
#define ELASTIO_SNAP_WAIT_FOR_RELEASE_MSEC 5000
#define ELASTIO_SNAP_WAIT_FOR_RELEASE_MAX_SLEEP_COUNT 1000
static void elastio_snap_wait_for_release(struct snap_device *dev);
#ifdef USE_BDOPS_SUBMIT_BIO
// Linux version 5.9+
static MRF_RETURN_TYPE snap_mrf(struct bio *bio);
#endif
static const struct block_device_operations snap_ops = {
.owner = THIS_MODULE,
.open = snap_open,
.release = snap_release,
#ifdef USE_BDOPS_SUBMIT_BIO
// Linux version 5.9+
.submit_bio = snap_mrf,
#endif
};
static const struct file_operations snap_control_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = ctrl_ioctl,
.compat_ioctl = ctrl_ioctl,
.open = nonseekable_open,
.llseek = noop_llseek,
};
static struct miscdevice snap_control_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = CONTROL_DEVICE_NAME,
.fops = &snap_control_fops,
};
static const struct seq_operations elastio_snap_seq_proc_ops = {
.start = elastio_snap_proc_start,
.next = elastio_snap_proc_next,
.stop = elastio_snap_proc_stop,
.show = elastio_snap_proc_show,
};
#ifdef HAVE_PROC_CREATE_FN_PROC_OPS
static const struct proc_ops elastio_snap_proc_fops = {
.proc_open = elastio_snap_proc_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = elastio_snap_proc_release,
};
#else
static const struct file_operations elastio_snap_proc_fops = {
.owner = THIS_MODULE,
.open = elastio_snap_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = elastio_snap_proc_release,
};
#endif
static int major;
static struct mutex ioctl_mutex;
static unsigned int highest_minor, lowest_minor;
static struct snap_device **snap_devices;
static struct proc_dir_entry *info_proc;
#if !SYS_MOUNT_ADDR
#if __X64_SYS_MOUNT_ADDR || __ARM64_SYS_MOUNT_ADDR
#define USE_ARCH_MOUNT_FUNCS
#else
#warning "No mount function found"
#endif
#endif
static int param_set_bio_stats(const char *buffer, const struct kernel_param *kp)
{
int i, minor;
struct snap_device *dev;
if (sscanf(buffer, "%d", &minor) != 1) return -EINVAL;
dev = snap_devices[minor];
if (!dev) return -ENODEV;
LOG_DEBUG("+-------------------------------+");
LOG_DEBUG("| Total bio request statistics |");
LOG_DEBUG("+--------+----------------------+");
LOG_DEBUG("| Type | Count |");
LOG_DEBUG("+--------+----------------------+");
for (i = 0; i < BIO_STATS_MAX_ELEMENTS; i++) {
if (dev->sd_bio_stats_total[i])
LOG_DEBUG("| %-3d | %-11lu |", i, dev->sd_bio_stats_total[i]);
}
LOG_DEBUG("+--------+----------------------+");
LOG_DEBUG("| Traced bio request statistics |");
LOG_DEBUG("+--------+----------------------+");
for (i = 0; i < BIO_STATS_MAX_ELEMENTS; i++) {
if (dev->sd_bio_stats_traced[i])
LOG_DEBUG("| %-3d | %-11lu |", i, dev->sd_bio_stats_traced[i]);
}
LOG_DEBUG("+--------+----------------------+");
return 0;
}
/*******************************ATOMIC FUNCTIONS******************************/
static inline int tracer_read_fail_state(const struct snap_device *dev){
smp_mb();
return atomic_read(&dev->sd_fail_code);
}
static inline void tracer_set_fail_state(struct snap_device *dev, int error){
smp_mb();
(void)atomic_cmpxchg(&dev->sd_fail_code, 0, error);
smp_mb();
}
static inline int wrap_err_io(struct snap_device *dev){
return !dev->sd_ignore_snap_errors ? -EIO : 0;
}
/************************IOCTL COPY FROM USER FUNCTIONS************************/
static int copy_string_from_user(const char __user *data, char **out_ptr){
int ret;
char *str;
if(!data){
*out_ptr = NULL;
return 0;
}
str = strndup_user(data, PAGE_SIZE);
if(IS_ERR(str)){
ret = PTR_ERR(str);
goto error;
}
*out_ptr = str;
return 0;
error:
LOG_ERROR(ret, "error copying string from user space");
*out_ptr = NULL;
return ret;
}
static int get_setup_params(const struct setup_params __user *in, unsigned int *minor, char **bdev_name, char **cow_path, unsigned long *fallocated_space, unsigned long *cache_size, bool *ignore_snap_errors){
int ret;
struct setup_params params;
//copy the params struct
ret = copy_from_user(¶ms, in, sizeof(struct setup_params));
if(ret){
ret = -EFAULT;
LOG_ERROR(ret, "error copying setup_params struct from user space");
goto error;
}
ret = copy_string_from_user((char __user *)params.bdev, bdev_name);
if(ret) goto error;
if(!*bdev_name){
ret = -EINVAL;
LOG_ERROR(ret, "NULL bdev given");
goto error;
}
ret = copy_string_from_user((char __user *)params.cow, cow_path);
if(ret) goto error;
if(!*cow_path){
ret = -EINVAL;
LOG_ERROR(ret, "NULL cow given");
goto error;
}
*minor = params.minor;
*fallocated_space = params.fallocated_space;
*cache_size = params.cache_size;
*ignore_snap_errors = params.ignore_snap_errors;
return 0;
error:
LOG_ERROR(ret, "error copying setup_params from user space");
if(*bdev_name) kfree(*bdev_name);
if(*cow_path) kfree(*cow_path);
*bdev_name = NULL;
*cow_path = NULL;
*minor = 0;
*fallocated_space = 0;
*cache_size = 0;
*ignore_snap_errors = false;
return ret;
}
static int get_reload_params(const struct reload_params __user *in, unsigned int *minor, char **bdev_name, char **cow_path, unsigned long *cache_size, bool *ignore_snap_errors){
int ret;
struct reload_params params;
//copy the params struct
ret = copy_from_user(¶ms, in, sizeof(struct reload_params));
if(ret){
ret = -EFAULT;
LOG_ERROR(ret, "error copying reload_params struct from user space");
goto error;
}
ret = copy_string_from_user((char __user *)params.bdev, bdev_name);
if(ret) goto error;
if(!*bdev_name){
ret = -EINVAL;
LOG_ERROR(ret, "NULL bdev given");
goto error;
}
ret = copy_string_from_user((char __user *)params.cow, cow_path);
if(ret) goto error;
if(!*cow_path){
ret = -EINVAL;
LOG_ERROR(ret, "NULL cow given");
goto error;
}
*minor = params.minor;
*cache_size = params.cache_size;
*ignore_snap_errors = params.ignore_snap_errors;
return 0;
error:
LOG_ERROR(ret, "error copying reload_params from user space");
if(*bdev_name) kfree(*bdev_name);
if(*cow_path) kfree(*cow_path);
*bdev_name = NULL;
*cow_path = NULL;
*minor = 0;
*cache_size = 0;
*ignore_snap_errors = false;
return ret;
}
static int get_transition_snap_params(const struct transition_snap_params __user *in, unsigned int *minor, char **cow_path, unsigned long *fallocated_space){
int ret;
struct transition_snap_params params;
//copy the params struct
ret = copy_from_user(¶ms, in, sizeof(struct transition_snap_params));
if(ret){
ret = -EFAULT;
LOG_ERROR(ret, "error copying transition_snap_params struct from user space");
goto error;
}
ret = copy_string_from_user((char __user *)params.cow, cow_path);
if(ret) goto error;
if(!*cow_path){
ret = -EINVAL;
LOG_ERROR(ret, "NULL cow given");
goto error;
}
*minor = params.minor;
*fallocated_space = params.fallocated_space;
return 0;
error:
LOG_ERROR(ret, "error copying transition_snap_params from user space");
if(*cow_path) kfree(*cow_path);
*cow_path = NULL;
*minor = 0;
*fallocated_space = 0;
return ret;
}
static int get_reconfigure_params(const struct reconfigure_params __user *in, unsigned int *minor, unsigned long *cache_size){
int ret;
struct reconfigure_params params;
//copy the params struct
ret = copy_from_user(¶ms, in, sizeof(struct reconfigure_params));
if(ret){
ret = -EFAULT;
LOG_ERROR(ret, "error copying reconfigure_params struct from user space");
goto error;
}
*minor = params.minor;
*cache_size = params.cache_size;
return 0;
error:
LOG_ERROR(ret, "error copying reconfigure_params from user space");
*minor = 0;
*cache_size = 0;
return ret;
}
/******************************TASK WORK FUNCTIONS*******************************/
//reimplementation of task_work_run() to force fput() and mntput() to perform their work synchronously
#ifdef HAVE_TASK_STRUCT_TASK_WORKS_HLIST
static void task_work_flush(void){
struct task_struct *task = current;
struct hlist_head task_works;
struct hlist_node *pos;
raw_spin_lock_irq(&task->pi_lock);
hlist_move_list(&task->task_works, &task_works);
raw_spin_unlock_irq(&task->pi_lock);
if(unlikely(hlist_empty(&task_works))) return;
for(pos = task_works.first; pos->next; pos = pos->next);
for(;;){
struct hlist_node **pprev = pos->pprev;
struct task_work *twork = container_of(pos, struct task_work, hlist);
twork->func(twork);
if(pprev == &task_works.first) break;
pos = container_of(pprev, struct hlist_node, next);
}
}
#elif defined HAVE_TASK_STRUCT_TASK_WORKS_CB_HEAD
static void task_work_flush(void){
struct task_struct *task = current;
struct callback_head *work, *head, *next;
for(;;){
do{
work = ACCESS_ONCE(task->task_works);
head = NULL; //current should not be PF_EXITING
}while(cmpxchg(&task->task_works, work, head) != work);
if(!work) break;
raw_spin_lock_irq(&task->pi_lock);
raw_spin_unlock_irq(&task->pi_lock);
head = NULL;
do{
next = work->next;
work->next = head;
head = work;
work = next;
}while(work);
work = head;
do{
next = work->next;
work->func(work);
work = next;
cond_resched();
}while(work);
}
}
#else
#define task_work_flush()
#endif
/******************************FILE OPERATIONS*******************************/
static inline void file_close(struct file *f){
filp_close(f, NULL);
}
static int file_open(const char *filename, int flags, struct file **filp){
int ret;
struct file *f;
f = filp_open(filename, flags | O_RDWR | O_LARGEFILE, 0);
if(!f){
ret = -EFAULT;
LOG_ERROR(ret, "error creating/opening file '%s' (null pointer)", filename);
goto error;
}else if(IS_ERR(f)){
ret = PTR_ERR(f);
f = NULL;
LOG_ERROR(ret, "error creating/opening file '%s' - %d", filename, ret);
goto error;
}else if(!S_ISREG(elastio_snap_get_dentry(f)->d_inode->i_mode)){
ret = -EINVAL;
LOG_ERROR(ret, "'%s' is not a regular file", filename);
goto error;
}
f->f_mode |= FMODE_NONOTIFY;
*filp = f;
return 0;
error:
LOG_ERROR(ret, "error opening file");
if(f) file_close(f);
*filp = NULL;
return ret;
}
#if !defined(HAVE___DENTRY_PATH) && !defined(HAVE_DENTRY_PATH_RAW)
static int dentry_get_relative_pathname(struct dentry *dentry, char **buf, int *len_res){
int len = 0;
char *pathname;
struct dentry *parent = dentry;
while(parent->d_parent != parent){
len += parent->d_name.len + 1;
parent = parent->d_parent;
}
pathname = kmalloc(len + 1, GFP_KERNEL);
if(!pathname){
LOG_ERROR(-ENOMEM, "error allocating pathname for dentry");
return -ENOMEM;
}
pathname[len] = '\0';
if(len_res) *len_res = len;
*buf = pathname;
parent = dentry;
while(parent->d_parent != parent){
len -= parent->d_name.len + 1;
pathname[len] = '/';
strncpy(&pathname[len + 1], parent->d_name.name, parent->d_name.len);
parent = parent->d_parent;
}
return 0;
}
#else
static int dentry_get_relative_pathname(struct dentry *dentry, char **buf, int *len_res){
int ret, len;
char *pathname, *page_buf, *final_buf = NULL;
page_buf = (char *)__get_free_page(GFP_KERNEL);
if(!page_buf){
LOG_ERROR(-ENOMEM, "error allocating page for dentry pathname");
return -ENOMEM;
}
#ifdef HAVE___DENTRY_PATH
spin_lock(&dcache_lock);
pathname = __dentry_path(dentry, page_buf, PAGE_SIZE);
spin_unlock(&dcache_lock);
#else
pathname = dentry_path_raw(dentry, page_buf, PAGE_SIZE);
#endif
if(IS_ERR(pathname)){
ret = PTR_ERR(pathname);
pathname = NULL;
LOG_ERROR(ret, "error fetching dentry pathname");
goto error;
}
len = page_buf + PAGE_SIZE - pathname;
final_buf = kmalloc(len, GFP_KERNEL);
if(!final_buf){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating pathname for dentry");
goto error;
}
strncpy(final_buf, pathname, len);
free_page((unsigned long)page_buf);
*buf = final_buf;
if(len_res) *len_res = len;
return 0;
error:
LOG_ERROR(ret, "error converting dentry to relative path name");
if(final_buf) kfree(final_buf);
if(page_buf) free_page((unsigned long)page_buf);
*buf = NULL;
if(len_res) *len_res = 0;
return ret;
}
#endif
static int path_get_absolute_pathname(const struct path *path, char **buf, int *len_res){
int ret, len;
char *pathname, *page_buf, *final_buf = NULL;
page_buf = (char *)__get_free_page(GFP_KERNEL);
if(!page_buf){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating page for absolute pathname");
goto error;
}
pathname = elastio_snap_d_path(path, page_buf, PAGE_SIZE);
if(IS_ERR(pathname)){
ret = PTR_ERR(pathname);
pathname = NULL;
LOG_ERROR(ret, "error fetching absolute pathname");
goto error;
}
len = page_buf + PAGE_SIZE - pathname;
final_buf = kmalloc(len, GFP_KERNEL);
if(!final_buf){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating buffer for absolute pathname");
goto error;
}
strncpy(final_buf, pathname, len);
free_page((unsigned long)page_buf);
*buf = final_buf;
if(len_res) *len_res = len;
return 0;
error:
LOG_ERROR(ret, "error getting absolute pathname from path");
if(final_buf) kfree(final_buf);
if(page_buf) free_page((unsigned long)page_buf);
*buf = NULL;
if(len_res) *len_res = 0;
return ret;
}
static int file_get_absolute_pathname(const struct file *filp, char **buf, int *len_res){
struct path path;
int ret;
path.mnt = elastio_snap_get_mnt(filp);
path.dentry = elastio_snap_get_dentry(filp);
ret = path_get_absolute_pathname(&path, buf, len_res);
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error converting file to absolute pathname");
*buf = NULL;
*len_res = 0;
return ret;
}
static int pathname_to_absolute(const char *pathname, char **buf, int *len_res){
int ret;
struct path path = {};
ret = kern_path(pathname, LOOKUP_FOLLOW, &path);
if(ret){
return ret;
}
ret = path_get_absolute_pathname(&path, buf, len_res);
if(ret) goto error;
path_put(&path);
return 0;
error:
LOG_ERROR(ret, "error converting pathname to absolute pathname");
path_put(&path);
return ret;
}
static int pathname_concat(const char *pathname1, const char *pathname2, char **path_out){
int pathname1_len, pathname2_len, need_leading_slash = 0;
char *full_pathname;
pathname1_len = strlen(pathname1);
pathname2_len = strlen(pathname2);
if(pathname1[pathname1_len - 1] != '/' && pathname2[0] != '/') need_leading_slash = 1;
else if(pathname1[pathname1_len - 1] == '/' && pathname2[0] == '/') pathname1_len--;
full_pathname = kmalloc(pathname1_len + pathname2_len + need_leading_slash + 1, GFP_KERNEL);
if(!full_pathname){
LOG_ERROR(-ENOMEM, "error allocating buffer for pathname concatenation");
*path_out = NULL;
return -ENOMEM;
}
full_pathname[pathname1_len + need_leading_slash + pathname2_len] = '\0';
strncpy(full_pathname, pathname1, pathname1_len);
if(need_leading_slash) full_pathname[pathname1_len] = '/';
strncpy(full_pathname + pathname1_len + need_leading_slash, pathname2, pathname2_len);
*path_out = full_pathname;
return 0;
}
static inline bool pathname_exists(const char *pathname){
int ret;
struct path path = {};
ret = kern_path(pathname, LOOKUP_FOLLOW, &path);
if(ret){
return false;
}
path_put(&path);
return true;
}
static int user_mount_pathname_concat(const char *user_mount_path, const char *rel_path, char **path_out, int is_user_space){
int ret;
char *mount_path;
if (is_user_space){
ret = copy_string_from_user(user_mount_path, &mount_path);
if(ret) goto error;
} else {
mount_path = kstrdup(user_mount_path, GFP_KERNEL);
if (!mount_path) {
ret = -ENOMEM;
goto error;
}
}
ret = pathname_concat(mount_path, rel_path, path_out);
if(ret) goto error;
kfree(mount_path);
return 0;
error:
LOG_ERROR(ret, "error concatenating mount path to relative path");
if(mount_path) kfree(mount_path);
*path_out = NULL;
return ret;
}
#define SECTOR_INVALID ~(u64)0
static sector_t sector_by_offset(struct snap_device *dev, size_t offset)
{
unsigned int i;
struct fiemap_extent *extent = dev->sd_cow_extents;
for (i = 0; i < dev->sd_cow_ext_cnt; i++) {
if (offset >= extent[i].fe_logical && offset < extent[i].fe_logical + extent[i].fe_length)
return (extent[i].fe_physical + (offset - extent[i].fe_logical)) >> 9;
}
return SECTOR_INVALID;
}
static int file_write_block(struct snap_device *dev, void *block, size_t offset, size_t len)
{
int ret;
int bytes;
char *data;
struct page *pg;
struct bio_set *bs;
struct bio *new_bio;
struct block_device *bdev;
sector_t start_sect;
int sectors_processed;
int iterations_done;
int bytes_written;
ret = 0;
bs = dev_bioset(dev);
bdev = dev->sd_base_dev;
sectors_processed = 0;
WARN_ON(len > SECTORS_PER_BLOCK);
write_bio:
start_sect = sector_by_offset(dev, offset);
if (start_sect == SECTOR_INVALID) {
LOG_WARN("Possible write IO to the end of file (offset=%lu)", offset);
ret = -EFAULT;
goto out;
}
#ifdef HAVE_BIO_ALLOC_2
new_bio = bio_alloc(GFP_NOIO, 1);
#else
new_bio = bio_alloc(bdev, 1, 0, GFP_KERNEL);
#endif
if(!new_bio){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating bio (write) - bs = %p", bs);
goto out;
}
elastio_snap_bio_set_dev(new_bio, bdev);
elastio_snap_set_bio_ops(new_bio, REQ_OP_WRITE, 0);
bio_sector(new_bio) = start_sect;
bio_idx(new_bio) = 0;
pg = alloc_page(GFP_NOIO);
if(!pg){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating read bio page");
goto out;
}
data = kmap(pg);
iterations_done = 0;
bytes_written = 0;
do {
bytes_written = iterations_done * SECTOR_SIZE;
memcpy(data + bytes_written, block + sectors_processed * SECTOR_SIZE, SECTOR_SIZE);
offset += SECTOR_SIZE;
sectors_processed++;
iterations_done++;
} while (sectors_processed < len &&
sector_by_offset(dev, offset) == start_sect + iterations_done);
kunmap(pg);
bytes_written = iterations_done * SECTOR_SIZE;
bytes = bio_add_page(new_bio, pg, bytes_written, 0);
if(bytes != bytes_written){
LOG_DEBUG("bio_add_page() error!");
__free_page(pg);
ret = -EFAULT;
goto out;
}
if (dev->sd_cow_inode)
pg->mapping = dev->sd_cow_inode->i_mapping;
ret = elastio_snap_submit_bio_wait(new_bio);
if (ret) {
LOG_ERROR(ret, "submit_bio_wait() error!");
goto out;
}
pg->mapping = NULL;
bio_free_pages(new_bio);
bio_put(new_bio);
new_bio = NULL;
if (sectors_processed != len)
goto write_bio;
out:
if (new_bio) {
pg->mapping = NULL;
bio_free_pages(new_bio);
bio_put(new_bio);
}
return ret;
}
static int file_read_block(struct snap_device *dev, void *buf, size_t offset, size_t len)
{
int ret;
int bytes;
struct page *pg;
struct bio_set *bs;
struct bio *new_bio;
struct block_device *bdev;
sector_t start_sect;
struct bio_vec *bvec;
#ifdef HAVE_BVEC_ITER_ALL
struct bvec_iter_all iter;
#else
int i = 0;
#endif
int sectors_processed;
int iterations_done;
int bytes_to_read;
int buf_offset;
ret = 0;
bs = dev_bioset(dev);
bdev = dev->sd_base_dev;
sectors_processed = 0;
WARN_ON(len > SECTORS_PER_BLOCK);
read_bio:
start_sect = sector_by_offset(dev, offset);
if (start_sect == SECTOR_INVALID) {
LOG_WARN("Possible read IO to the end of file (offset=%lu)", offset);
ret = -EFAULT;
goto out;
}
#ifdef HAVE_BIO_ALLOC_2
new_bio = bio_alloc(GFP_NOIO, 1);
#else
new_bio = bio_alloc(bdev, 1, 0, GFP_KERNEL);
#endif
if(!new_bio){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating bio (read) - bs = %p", bs);
goto out;
}
elastio_snap_bio_set_dev(new_bio, bdev);
elastio_snap_set_bio_ops(new_bio, REQ_OP_READ, 0);
bio_sector(new_bio) = start_sect;
bio_idx(new_bio) = 0;
//allocate a page and add it to our bio
pg = alloc_page(GFP_NOIO);
if(!pg){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating read bio page");
goto out;
}
iterations_done = 0;
bytes_to_read = 0;
buf_offset = sectors_processed * SECTOR_SIZE;
do {
offset += SECTOR_SIZE;
sectors_processed++;
iterations_done++;
} while (sectors_processed < len &&
sector_by_offset(dev, offset) == start_sect + iterations_done);
bytes_to_read = iterations_done * SECTOR_SIZE;
bytes = bio_add_page(new_bio, pg, bytes_to_read, 0);
if(bytes != bytes_to_read){
LOG_DEBUG("bio_add_page() error!");
__free_page(pg);
ret = -EFAULT;
goto out;
}
if (dev->sd_cow_inode)
pg->mapping = dev->sd_cow_inode->i_mapping;
ret = elastio_snap_submit_bio_wait(new_bio);
if (ret) {
LOG_ERROR(ret, "submit_bio_wait() error!");
goto out;
}
#ifdef HAVE_BVEC_ITER_ALL
bio_for_each_segment_all(bvec, new_bio, iter) {
#else
bio_for_each_segment_all(bvec, new_bio, i) {
#endif
struct page *pg = bvec->bv_page;
char *data = kmap(pg);
WARN_ON(bytes_to_read != bvec->bv_len);
memcpy(buf + buf_offset, data, bytes_to_read);
kunmap(pg);
// in an impossible case if we have more
// than one page (should never happen)
break;
}
pg->mapping = NULL;
bio_free_pages(new_bio);
bio_put(new_bio);
new_bio = NULL;
if (sectors_processed != len)
goto read_bio;
out:
if (new_bio) {
pg->mapping = NULL;
bio_free_pages(new_bio);
bio_put(new_bio);
}
return ret;
}
static inline ssize_t elastio_snap_kernel_read(struct cow_manager *cm, void *buf, size_t count, loff_t *pos){
ssize_t ret;
if (cm->filp) {
#ifndef HAVE_KERNEL_READ_PPOS
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
mm_segment_t old_fs;
old_fs = get_fs();
set_fs(get_ds());
ret = vfs_read(cm->filp, (char __user *)buf, count, pos);
set_fs(old_fs);
return ret;
#else
ret = kernel_read(cm->filp, buf, count, pos);
return ret;
#endif
} else {
WARN_ON(count % SECTOR_SIZE != 0 || count < SECTOR_SIZE);
LOG_DEBUG("DIO: reading %lu sectors...", count / SECTOR_SIZE);
ret = file_read_block(cm->dev, buf, *pos, count / SECTOR_SIZE);
if (!ret) ret = count;
return ret;
}
}
static inline ssize_t elastio_snap_kernel_write(struct cow_manager *cm, void *buf, size_t count, loff_t *pos){
ssize_t ret;
if (cm->filp) {
#ifndef HAVE_KERNEL_WRITE_PPOS
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)
mm_segment_t old_fs;
old_fs = get_fs();
set_fs(get_ds());
ret = vfs_write(cm->filp, (__force const char __user *)buf, count, pos);
set_fs(old_fs);
return ret;
#else
ret = kernel_write(cm->filp, buf, count, pos);
return ret;
#endif
} else {
WARN_ON(count % SECTOR_SIZE != 0 || count < SECTOR_SIZE);
LOG_DEBUG("DIO: writing %lu sectors...", count / SECTOR_SIZE);
ret = file_write_block(cm->dev, buf, *pos, count / SECTOR_SIZE);
if (!ret) ret = count;
return ret;
}
}
static int file_io(struct cow_manager *cm, int is_write, void *buf, sector_t offset, unsigned long len){
ssize_t ret;
loff_t off = (loff_t)offset;
if(is_write) ret = elastio_snap_kernel_write(cm, buf, len, &off);
else ret = elastio_snap_kernel_read(cm, buf, len, &off);
if(ret < 0){
LOG_ERROR((int)ret, "error performing file '%s': %llu, %lu", (is_write)? "write" : "read", (unsigned long long)offset, len);
return ret;
}else if(ret != len){
LOG_ERROR(-EIO, "invalid file '%s' size: %llu, %lu, %lu", (is_write)? "write" : "read", (unsigned long long)offset, len, (unsigned long)ret);
ret = -EIO;
return ret;
}
return 0;
}
#define file_write(cm, buf, offset, len) file_io(cm, 1, buf, offset, len)
#define file_read(cm, buf, offset, len) file_io(cm, 0, buf, offset, len)
//reimplemented from linux kernel (it isn't exported in the vanilla kernel)
static int elastio_snap_do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, struct file *filp){
int ret;
struct iattr newattrs;
if(length < 0) return -EINVAL;
newattrs.ia_size = length;
newattrs.ia_valid = ATTR_SIZE | time_attrs;
if(filp) {
newattrs.ia_file = filp;
newattrs.ia_valid |= ATTR_FILE;
}
ret = elastio_snap_should_remove_suid(dentry);
if(ret) newattrs.ia_valid |= ret | ATTR_FORCE;
elastio_snap_inode_lock(dentry->d_inode);
#ifdef HAVE_NOTIFY_CHANGE_2
//#if LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)
ret = notify_change(dentry, &newattrs);
#elif defined HAVE_NOTIFY_CHANGE_3
//#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0)
ret = notify_change(dentry, &newattrs, NULL);
#elif defined HAVE_NOTIFY_CHANGE_4_USER_NAMESPACE
//#if LINUX_VERSION_CODE < KERNEL_VERSION(6,3,0)
ret = notify_change(&init_user_ns, dentry, &newattrs, NULL);
#else
ret = notify_change(file_mnt_idmap(filp), dentry, &newattrs, NULL);
#endif
elastio_snap_inode_unlock(dentry->d_inode);
return ret;
}
static int file_truncate(struct file *filp, loff_t len){
struct inode *inode;
struct dentry *dentry;
int ret;
dentry = elastio_snap_get_dentry(filp);
inode = dentry->d_inode;
#ifdef HAVE_LOCKS_VERIFY_TRUNCATE
// The function has been disappeared starting from the kernel 5.15.
ret = locks_verify_truncate(inode, filp, len);
#else
ret = vfs_truncate(&filp->f_path, len);
#endif
if(ret){
LOG_ERROR(ret, "error verifying truncation is possible");
goto error;
}
#ifdef HAVE_SB_START_WRITE
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)
sb_start_write(inode->i_sb);
#endif
ret = elastio_snap_do_truncate(dentry, len, ATTR_MTIME|ATTR_CTIME, filp);
#ifdef HAVE_SB_START_WRITE
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)
sb_end_write(inode->i_sb);
#endif
if(ret){
LOG_ERROR(ret, "error performing truncation");
goto error;
}
return 0;
error:
LOG_ERROR(ret, "error truncating file");
return ret;
}
#ifdef HAVE_VFS_FALLOCATE
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0)
#define real_fallocate(f, offset, length) vfs_fallocate(f, 0, offset, length)
#else
static int real_fallocate(struct file *f, uint64_t offset, uint64_t length){
int ret;
loff_t off = offset;
loff_t len = length;
#ifndef HAVE_FILE_INODE
//#if LINUX_VERSION_CODE < KERNEL_VERSION(3,9,0)
struct inode *inode = elastio_snap_get_dentry(f)->d_inode;
#else
struct inode *inode = file_inode(f);
#endif
if(off + len > inode->i_sb->s_maxbytes || off + len < 0) return -EFBIG;
#if !defined(HAVE_IOPS_FALLOCATE) && !defined(HAVE_FOPS_FALLOCATE)
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
return -EOPNOTSUPP;
#elif defined(HAVE_IOPS_FALLOCATE)
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,38)
if(!inode->i_op->fallocate) return -EOPNOTSUPP;
ret = inode->i_op->fallocate(inode, 0, offset, len);
#else
if(!f->f_op->fallocate) return -EOPNOTSUPP;
#ifdef HAVE_SB_START_WRITE
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)
sb_start_write(inode->i_sb);
#endif
ret = f->f_op->fallocate(f, 0, off, len);
#ifdef HAVE_SB_START_WRITE
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,6,0)
sb_end_write(inode->i_sb);
#endif
#endif
return ret;
}
#endif
static int file_allocate(struct cow_manager *cm, struct file *f, uint64_t offset, uint64_t length){
int ret = 0;
char *page_buf = NULL;
uint64_t i, write_count;
char *abs_path = NULL;
int abs_path_len;
file_get_absolute_pathname(f, &abs_path, &abs_path_len);
//try regular fallocate
ret = real_fallocate(f, offset, length);
if(ret && ret != -EOPNOTSUPP) goto error;
else if(!ret) goto out;
//fallocate isn't supported, fall back on writing zeros
LOG_WARN("fallocate is not supported for %s, falling back on writing zeros",
(abs_path)? abs_path : "this file system");
//allocate page of zeros
page_buf = (char *)get_zeroed_page(GFP_KERNEL);
if(!page_buf){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating zeroed page");
goto error;
}
//may write up to a page too much, ok for our use case
write_count = NUM_SEGMENTS(length, PAGE_SHIFT);
LOG_DEBUG("allocating cow file (%llu bytes)", PAGE_SIZE * write_count);
//if not page aligned, write zeros to that point
if(offset % PAGE_SIZE != 0){
ret = file_write(cm, page_buf, offset, PAGE_SIZE - (offset % PAGE_SIZE));
if(ret) goto error;
offset += PAGE_SIZE - (offset % PAGE_SIZE);
}
//write a page of zeros at a time
for(i = 0; i < write_count; i++){
ret = file_write(cm, page_buf, offset + (PAGE_SIZE * i), PAGE_SIZE);
if(ret) goto error;
cond_resched();
}
out:
ret = vfs_fsync(f, 0);
if (ret) {
LOG_ERROR(ret, "could not sync the file, but let's continue");
}
if(page_buf) free_page((unsigned long)page_buf);
if(abs_path) kfree(abs_path);
return 0;
error:
if(!abs_path){
LOG_ERROR(ret, "error performing fallocate");
}else{
LOG_ERROR(ret, "error performing fallocate on file '%s'", abs_path);
}
if(page_buf) free_page((unsigned long)page_buf);
if(abs_path) kfree(abs_path);
return ret;
}
static int __file_unlink(struct file *filp, int close, int force){
int ret = 0;
struct inode *dir_inode = elastio_snap_get_dentry(filp)->d_parent->d_inode;
struct dentry *file_dentry = elastio_snap_get_dentry(filp);
struct vfsmount *mnt = elastio_snap_get_mnt(filp);
if(d_unlinked(file_dentry)){
if(close) file_close(filp);
return 0;
}
dget(file_dentry);
igrab(dir_inode);
ret = mnt_want_write(mnt);
if(ret){
LOG_ERROR(ret, "error getting write access to vfs mount");
goto mnt_error;
}
#ifdef HAVE_VFS_UNLINK_2
//#if LINUX_VERSION_CODE < KERNEL_VERSION(3,13,0)
ret = vfs_unlink(dir_inode, file_dentry);
#elif defined HAVE_VFS_UNLINK_3
//#if LINUX_VERSION_CODE < KERNEL_VERSION(5,12,0)
ret = vfs_unlink(dir_inode, file_dentry, NULL);
#elif defined HAVE_VFS_UNLINK_4_USER_NAMESPACE
//#if LINUX_VERSION_CODE < KERNEL_VERSION(6,3,0)
ret = vfs_unlink(&init_user_ns, dir_inode, file_dentry, NULL);
#else
ret = vfs_unlink(file_mnt_idmap(filp), dir_inode, file_dentry, NULL);
#endif
if(ret){
LOG_ERROR(ret, "error unlinking file");
goto error;
}
error:
mnt_drop_write(mnt);
if(close && (!ret || force)) file_close(filp);
mnt_error:
iput(dir_inode);
dput(file_dentry);
return ret;
}
#define file_unlink(filp) __file_unlink(filp, 0, 0)
#define file_unlink_and_close(filp) __file_unlink(filp, 1, 0)
#define file_unlink_and_close_force(filp) __file_unlink(filp, 1, 1)
/***************************COW MANAGER FUNCTIONS**************************/
static void __cow_free_section(struct cow_manager *cm, unsigned long sect_idx){
free_pages((unsigned long)cm->sects[sect_idx].mappings, cm->log_sect_pages);
cm->sects[sect_idx].mappings = NULL;
cm->allocated_sects--;
}
static int __cow_alloc_section(struct cow_manager *cm, unsigned long sect_idx, int zero){
if(zero) cm->sects[sect_idx].mappings = (void*)get_zeroed_pages(GFP_KERNEL, cm->log_sect_pages);
else cm->sects[sect_idx].mappings = (void*)__get_free_pages(GFP_KERNEL, cm->log_sect_pages);
if(!cm->sects[sect_idx].mappings){
LOG_ERROR(-ENOMEM, "failed to allocate mappings at index %lu", sect_idx);
return -ENOMEM;
}
cm->sects[sect_idx].has_data = 1;
cm->allocated_sects++;
return 0;
}
static int __cow_load_section(struct cow_manager *cm, unsigned long sect_idx){
int i, ret;
int sect_size_bytes = COW_SECTION_SIZE * sizeof(uint64_t);
ret = __cow_alloc_section(cm, sect_idx, 0);
if(ret) goto error;
for (i = 0; i < sect_size_bytes / COW_BLOCK_SIZE; i++) {
int mapping_offset = (COW_BLOCK_SIZE / sizeof(cm->sects[sect_idx].mappings[0])) * i;
int cow_file_offset = COW_BLOCK_SIZE * i;
ret = file_read(cm, cm->sects[sect_idx].mappings + mapping_offset, COW_HEADER_SIZE + cm->sect_size*sect_idx * sizeof(uint64_t) + cow_file_offset, COW_BLOCK_SIZE);
if(ret) goto error;
}
return 0;
error:
LOG_ERROR(ret, "error loading section from file");
if(cm->sects[sect_idx].mappings) __cow_free_section(cm, sect_idx);
return ret;
}
static int __cow_write_section(struct cow_manager *cm, unsigned long sect_idx){
int i, ret;
int sect_size_bytes = COW_SECTION_SIZE * sizeof(uint64_t);
for (i = 0; i < sect_size_bytes / COW_BLOCK_SIZE; i++) {
int mapping_offset = (COW_BLOCK_SIZE / sizeof(cm->sects[sect_idx].mappings[0])) * i;
int cow_file_offset = COW_BLOCK_SIZE * i;
ret = file_write(cm, cm->sects[sect_idx].mappings + mapping_offset, COW_HEADER_SIZE + cm->sect_size*sect_idx * sizeof(uint64_t) + cow_file_offset, COW_BLOCK_SIZE);
if(ret){
LOG_ERROR(ret, "error writing cow manager section to file");
return ret;
}
}
return 0;
}
static int __cow_file_extents_zero_fill_ahead(struct cow_manager *cm)
{
int ret;
char *buf, *page;
uint64_t write_max = COW_BLOCK_SIZE * 1024;
uint64_t curr_size = cm->curr_pos * COW_BLOCK_SIZE;
uint64_t write_ahead = min(cm->file_max - curr_size, write_max);
uint64_t remaining;
buf = kmalloc(write_ahead, GFP_KERNEL);
if (!buf) {
remaining = write_ahead;
/* falls back when error allocating a 4MB contiguous buffer with kmalloc.
* Instead allocate a single zeroed page and write in PAGE_SIZE chunks.
* This avoids high-order allocation failures under memory fragmentation.
*/
page = (char *)get_zeroed_page(GFP_KERNEL);
if (!page) {
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating zeroed page for write-ahead");
return ret;
}
while (remaining != 0) {
uint64_t chunk = min((uint64_t)PAGE_SIZE, remaining);
ret = file_write(cm, page, curr_size, chunk);
if (ret < 0) {
free_page((unsigned long)page);
LOG_ERROR(ret, "error writing zero page during write-ahead");
return ret;
}
remaining -= chunk;
curr_size += chunk;
cond_resched();
}
free_page((unsigned long)page);
return 0;
}
/* This covers the case when the module is removed in the dormant state.
* In such a case, the direct write to the COW file takes place to write
* the rest of bio requests which have not been processed yet. However,
* it may (and likely will) happen that the COW file extents, which is
* being used for the direct write, aren't initialized by the FS. Hence,
* if we simply write there, there's no chance this data will be available
* at the next volume mount. To make this possible, we write some data
* ahead to ensure the extent is valid during the direct write operation
*/
ret = file_write(cm, buf, curr_size, write_ahead);
kfree(buf);
return ret;
}
static int __cow_sync_and_free_sections(struct cow_manager *cm, unsigned long thresh, bool fill_ahead){
int ret;
unsigned long i;
for(i=0; i<cm->total_sects && (!thresh || cm->allocated_sects > cm->allowed_sects/2); i++){
if(cm->sects[i].mappings && (!thresh || cm->sects[i].usage <= thresh)){
ret = __cow_write_section(cm, i);
if(ret){
LOG_ERROR(ret, "error writing cow manager section %lu to file", i);
return ret;
}
__cow_free_section(cm, i);
}
cm->sects[i].usage = 0;
}
if (fill_ahead && __cow_file_extents_zero_fill_ahead(cm)) {
LOG_ERROR(-EIO, "couldn't prepare cow file extents, data may be corrupted");
return -EIO;
}
return 0;
}
static int __cow_cleanup_mappings(struct cow_manager *cm){
int ret;
unsigned long less, greater, i, granularity, thresh = 0;
//find the max usage of the sections of the cm
for(i=0; i<cm->total_sects; i++){
if(cm->sects[i].usage > thresh) thresh = cm->sects[i].usage;
}
//find the (approximate) median usage of the sections of the cm
thresh /= 2;
granularity = thresh;
while(granularity > 0){
granularity = granularity >> 1;
less = 0;
greater = 0;
for(i=0; i<cm->total_sects; i++){
if(cm->sects[i].usage <= thresh) less++;
else greater++;
}
if(greater > less) thresh += granularity;
else if(greater < less) thresh -= granularity;
else break;
}
//deallocate sections of the cm with less usage than the median
ret = __cow_sync_and_free_sections(cm, thresh, false);
if(ret){
LOG_ERROR(ret, "error cleaning cow manager mappings");
return ret;
}
return 0;
}
static int __cow_write_header(struct cow_manager *cm, int is_clean){
int ret;
struct cow_header *ch = kzalloc(COW_HEADER_SIZE, GFP_KERNEL);
if (!ch) {
LOG_ERROR(-ENOMEM, "allocation failed");
return -ENOMEM;
}
if(is_clean) cm->flags |= (1 << COW_CLEAN);
else cm->flags &= ~(1 << COW_CLEAN);
ch->magic = COW_MAGIC;
ch->flags = cm->flags;
ch->fpos = cm->curr_pos;
ch->fsize = cm->file_max;
ch->seqid = cm->seqid;
memcpy(ch->uuid, cm->uuid, COW_UUID_SIZE);
ch->version = cm->version;
ch->nr_changed_blocks = cm->nr_changed_blocks;
ret = file_write(cm, ch, 0, COW_HEADER_SIZE);
if(ret){
LOG_ERROR(ret, "error syncing cow manager header");
kfree(ch);
return ret;
}
kfree(ch);
return 0;
}
#define __cow_write_header_dirty(cm) __cow_write_header(cm, 0)
#define __cow_close_header(cm) __cow_write_header(cm, 1)
static int __cow_open_header(struct cow_manager *cm, int index_only, int reset_vmalloc){
int ret;
struct cow_header *ch = kzalloc(COW_HEADER_SIZE, GFP_KERNEL);
if (!ch) {
LOG_ERROR(-ENOMEM, "allocation failed");
return -ENOMEM;
}
ret = file_read(cm, ch, 0, COW_HEADER_SIZE);
if(ret) goto error;
if(ch->magic != COW_MAGIC){
ret = -EINVAL;
LOG_ERROR(-EINVAL, "bad magic number found in cow file: %lu", ((unsigned long)ch->magic));
goto error;
}
if(!(ch->flags & (1 << COW_CLEAN))){
ret = -EINVAL;
LOG_ERROR(-EINVAL, "cow file not left in clean state: %lu", ((unsigned long)ch->flags));
goto error;
}
if(((ch->flags & (1 << COW_INDEX_ONLY)) && !index_only) || (!(ch->flags & (1 << COW_INDEX_ONLY)) && index_only)){
ret = -EINVAL;
LOG_ERROR(-EINVAL, "cow file not left in %s state: %lu", ((index_only)? "index only" : "data tracking"), (unsigned long)ch->flags);
goto error;
}
LOG_DEBUG("cow header opened with file pos = %llu, seqid = %llu", ((unsigned long long)ch->fpos), (unsigned long long)ch->seqid);
if(reset_vmalloc) cm->flags = ch->flags & ~(1 << COW_VMALLOC_UPPER);
else cm->flags = ch->flags;
cm->curr_pos = ch->fpos;
cm->file_max = ch->fsize;
cm->seqid = ch->seqid;
memcpy(cm->uuid, ch->uuid, COW_UUID_SIZE);
cm->version = ch->version;
cm->nr_changed_blocks = ch->nr_changed_blocks;
ret = __cow_write_header_dirty(cm);
if(ret) goto error;
kfree(ch);
return 0;
error:
LOG_ERROR(ret, "error opening cow manager header");
kfree(ch);
return ret;
}
static void cow_free_members(struct cow_manager *cm){
unsigned long i;
if(cm->sects){
for(i = 0; i < cm->total_sects; i++){
if(cm->sects[i].mappings) free_pages((unsigned long)cm->sects[i].mappings, cm->log_sect_pages);
}
if(cm->flags & (1 << COW_VMALLOC_UPPER)) vfree(cm->sects);
else kfree(cm->sects);
cm->sects = NULL;
}
if(cm->filp){
file_unlink_and_close_force(cm->filp);
cm->filp = NULL;
}
}
static void cow_free(struct cow_manager *cm){
cow_free_members(cm);
kfree(cm);
}
static int cow_sync_and_free(struct cow_manager *cm){
int ret;
ret = __cow_sync_and_free_sections(cm, 0, false);
if(ret) goto error;
ret = __cow_close_header(cm);
if(ret) goto error;
if(cm->filp) file_close(cm->filp);
if(cm->sects){
if(cm->flags & (1 << COW_VMALLOC_UPPER)) vfree(cm->sects);
else kfree(cm->sects);
}
kfree(cm);
return 0;
error:
LOG_ERROR(ret, "error while syncing and freeing cow manager");
cow_free(cm);
return ret;
}
static inline void elastio_snap_mm_lock(struct mm_struct *mm)
{
#ifdef HAVE_MMAP_WRITE_LOCK
mmap_write_lock(mm);
#else
down_write(&mm->mmap_sem);
#endif
}
static inline void elastio_snap_mm_unlock(struct mm_struct *mm)
{
#ifdef HAVE_MMAP_WRITE_LOCK
mmap_write_unlock(mm);
#else
up_write(&mm->mmap_sem);
#endif
}
struct kmem_cache **vm_area_cache = (VM_AREA_CACHEP_ADDR != 0) ?
(struct kmem_cache **) (VM_AREA_CACHEP_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
#ifdef HAVE_VM_AREA_STRUCT_VM_LOCK
static struct kmem_cache **vma_lock_cache = (VMA_LOCK_CACHEP_ADDR != 0) ?
(struct kmem_cache **) (VMA_LOCK_CACHEP_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
#endif
static struct vm_area_struct *elastio_snap_vm_area_allocate(struct mm_struct *mm)
{
struct vm_area_struct *vma;
static const struct vm_operations_struct dummy_vm_ops = {};
if (!vm_area_cache) {
LOG_ERROR(-ENOTSUPP, "vm_area_cachep was not found");
return NULL;
}
vma = kmem_cache_zalloc(*vm_area_cache, GFP_KERNEL);
if (!vma) {
LOG_ERROR(-ENOMEM, "kmem_cache_zalloc() failed");
return NULL;
}
#ifdef HAVE_VM_AREA_STRUCT_VM_LOCK
vma->vm_lock = kmem_cache_zalloc(*vma_lock_cache, GFP_KERNEL);
if (!vma->vm_lock) {
LOG_ERROR(-ENOMEM, "kmem_cache_zalloc() failed");
kmem_cache_free(*vm_area_cache, vma);
return NULL;
}
init_rwsem(&vma->vm_lock->lock);
vma->vm_lock_seq = -1;
#endif
vma->vm_mm = mm;
vma->vm_ops = &dummy_vm_ops;
INIT_LIST_HEAD(&vma->anon_vma_chain);
return vma;
}
static void elastio_snap_vm_area_free(struct vm_area_struct *vma)
{
kmem_cache_free(*vm_area_cache, vma);
}
static unsigned long elastio_snap_get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) {
#if __GET_UNMAPPED_AREA_ADDR
unsigned long (*__get_unmapped_area)(struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags, vm_flags_t vm_flags) = (__GET_UNMAPPED_AREA_ADDR != 0) ?
(unsigned long (*) (struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags, vm_flags_t vm_flags)) (__GET_UNMAPPED_AREA_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
return __get_unmapped_area(file, addr, len, pgoff, flags, 0);
#else
return get_unmapped_area(file, addr, len, pgoff, flags);
#endif
}
static int elastio_snap_get_cow_file_extents(struct snap_device *dev, struct file *filp)
{
int ret;
struct fiemap_extent_info fiemap_info;
unsigned int fiemap_mapped_extents_size, i_ext;
struct fiemap_extent *extent;
char parent_process_name[TASK_COMM_LEN];
unsigned long vm_flags = VM_READ | VM_WRITE;
unsigned long start_addr;
struct task_struct *task;
struct vm_area_struct *vma;
struct page *pg;
__user uint8_t *cow_ext_buf;
// we save it to fix its value till the end of the function
unsigned long cow_ext_buf_size = ALIGN(elastio_snap_cow_ext_buf_size, PAGE_SIZE);
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len);
int (*insert_vm_struct)(struct mm_struct *mm, struct vm_area_struct *vma) = (INSERT_VM_STRUCT_ADDR != 0) ?
(int (*)(struct mm_struct *mm, struct vm_area_struct *vma)) (INSERT_VM_STRUCT_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
if (!insert_vm_struct) {
LOG_ERROR(-ENOTSUPP, "insert_vm_struct() was not found");
return -ENOTSUPP;
}
fiemap = NULL;
task = get_current();
LOG_DEBUG("getting cow file extents from filp=%p", filp);
LOG_DEBUG("attempting page stealing from %s", get_task_comm(parent_process_name, task));
elastio_snap_mm_lock(task->mm);
start_addr = elastio_snap_get_unmapped_area(NULL, 0, cow_ext_buf_size, 0, VM_READ | VM_WRITE);
if (IS_ERR_VALUE(start_addr))
return start_addr; // returns -EPERM if failed
// we must give fiemap() a userspace buffer, so we
// brutally impose pages to the parent process
vma = elastio_snap_vm_area_allocate(task->mm);
if (!vma) {
ret = -ENOMEM;
LOG_ERROR(ret, "vm_area_alloc() failed");
elastio_snap_mm_unlock(task->mm);
return ret;
}
vma->vm_start = start_addr;
vma->vm_end = start_addr + cow_ext_buf_size;
*(unsigned long *) &vma->vm_flags = vm_flags;
vma->vm_page_prot = vm_get_page_prot(vm_flags);
vma->vm_pgoff = 0;
ret = insert_vm_struct(task->mm, vma);
if (ret < 0) {
ret = -EINVAL;
LOG_ERROR(ret, "insert_vm_struct() failed");
elastio_snap_vm_area_free(vma);
elastio_snap_mm_unlock(task->mm);
return ret;
}
pg = alloc_pages(GFP_USER, get_order(cow_ext_buf_size));
if (!pg) {
ret = -ENOMEM;
LOG_ERROR(ret, "alloc_page() failed");
elastio_snap_vm_area_free(vma);
elastio_snap_mm_unlock(task->mm);
return ret;
}
SetPageReserved(pg);
ret = remap_pfn_range(vma, vma->vm_start, page_to_pfn(pg), cow_ext_buf_size, PAGE_SHARED);
if (ret < 0) {
LOG_ERROR(ret, "remap_pfn_range() failed");
ClearPageReserved(pg);
__free_pages(pg, get_order(cow_ext_buf_size));
elastio_snap_vm_area_free(vma);
elastio_snap_mm_unlock(task->mm);
return ret;
}
cow_ext_buf = (__user uint8_t *) start_addr;
if (filp->f_inode->i_op)
fiemap = filp->f_inode->i_op->fiemap;
if (fiemap) {
int64_t fiemap_max = ~0ULL & ~(1ULL << 63);
int max_num_extents = cow_ext_buf_size; // used for do_div() as it overwrites the first argument
fiemap_info.fi_flags = FIEMAP_FLAG_SYNC;
fiemap_info.fi_extents_mapped = 0;
do_div(max_num_extents, sizeof(struct fiemap_extent));
fiemap_info.fi_extents_max = max_num_extents;
fiemap_info.fi_extents_start = (struct fiemap_extent __user *)cow_ext_buf;
ret = fiemap(filp->f_inode, &fiemap_info, 0, fiemap_max);
LOG_DEBUG("fiemap for cow file (ret %d), extents %u (max %u)", ret,
fiemap_info.fi_extents_mapped, fiemap_info.fi_extents_max);
if (!ret && fiemap_info.fi_extents_mapped > 0) {
if (dev->sd_cow_extents) kfree(dev->sd_cow_extents);
fiemap_mapped_extents_size = fiemap_info.fi_extents_mapped * sizeof(struct fiemap_extent);
dev->sd_cow_extents = kmalloc(fiemap_mapped_extents_size, GFP_KERNEL);
if (dev->sd_cow_extents) {
ret = copy_from_user(dev->sd_cow_extents, cow_ext_buf, fiemap_mapped_extents_size);
if (!ret) {
dev->sd_cow_ext_cnt = fiemap_info.fi_extents_mapped;
WARN(dev->sd_cow_ext_cnt == max_num_extents, "max num of extents read, increase cow_ext_buf_size");
extent = dev->sd_cow_extents;
for (i_ext = 0; i_ext < fiemap_info.fi_extents_mapped; ++i_ext, ++extent) {
LOG_DEBUG(" cow file extent: log 0x%llx, phy 0x%llx, len %llu", extent->fe_logical, extent->fe_physical, extent->fe_length);
}
}
}
}
} else {
ret = -ENOTSUPP;
LOG_ERROR(ret, "fiemap not supported");
goto out;
}
out:
ClearPageReserved(pg);
elastio_snap_mm_unlock(task->mm);
vm_munmap(vma->vm_start, cow_ext_buf_size);
__free_pages(pg, get_order(cow_ext_buf_size));
return ret;
}
static int cow_sync_and_close(struct cow_manager *cm){
int ret;
ret = __cow_sync_and_free_sections(cm, 0, true);
if(ret) goto error;
ret = __cow_close_header(cm);
if(ret) goto error;
ret = elastio_snap_get_cow_file_extents(cm->dev, cm->filp);
if(ret) goto error;
if(cm->filp) file_close(cm->filp);
cm->filp = NULL;
return 0;
error:
LOG_ERROR(ret, "error while syncing and closing cow manager");
cow_free_members(cm);
return ret;
}
static int cow_reopen(struct cow_manager *cm, const char *pathname){
int ret;
LOG_DEBUG("reopening cow file");
ret = file_open(pathname, 0, &cm->filp);
if(ret) goto error;
cm->dev->sd_cow_inode = cm->filp->f_inode;
LOG_DEBUG("opening cow header");
ret = __cow_open_header(cm, (cm->flags & (1 << COW_INDEX_ONLY)), 0);
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error reopening cow manager");
if(cm->filp) file_close(cm->filp);
cm->filp = NULL;
return ret;
}
static unsigned long __cow_calculate_allowed_sects(unsigned long cache_size, unsigned long total_sects){
if(cache_size <= (total_sects * sizeof(struct cow_section))) return 0;
else return (cache_size - (total_sects * sizeof(struct cow_section))) / (COW_SECTION_SIZE * sizeof(uint64_t));
}
static int cow_reload(struct snap_device *dev, const char *path, uint64_t elements, unsigned long sect_size, unsigned long cache_size, int index_only, struct cow_manager **cm_out){
int ret;
struct cow_manager *cm;
LOG_DEBUG("allocating cow manager");
cm = kzalloc(sizeof(struct cow_manager), GFP_KERNEL);
if(!cm){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating cow manager");
goto error;
}
LOG_DEBUG("opening cow file");
ret = file_open(path, 0, &cm->filp);
if(ret) goto error;
cm->allocated_sects = 0;
cm->sect_size = sect_size;
cm->log_sect_pages = get_order(sect_size * sizeof(uint64_t));
cm->total_sects = NUM_SEGMENTS(elements, cm->log_sect_pages + PAGE_SHIFT - 3);
cm->allowed_sects = __cow_calculate_allowed_sects(cache_size, cm->total_sects);
cm->data_offset = COW_HEADER_SIZE + (cm->total_sects * (sect_size * sizeof(uint64_t)));
dev->sd_cow_inode = cm->filp->f_inode;
cm->dev = dev;
ret = __cow_open_header(cm, index_only, 1);
if(ret) goto error;
LOG_DEBUG("allocating cow manager array (%lu sections)", cm->total_sects);
cm->sects = kzalloc((cm->total_sects) * sizeof(struct cow_section), GFP_KERNEL | __GFP_NOWARN);
if(!cm->sects){
//try falling back to vmalloc
cm->flags |= (1 << COW_VMALLOC_UPPER);
cm->sects = vzalloc((cm->total_sects) * sizeof(struct cow_section));
if(!cm->sects){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating cow manager sects array");
goto error;
}
}
*cm_out = cm;
return 0;
error:
LOG_ERROR(ret, "error during cow manager initialization");
if(cm->filp) file_close(cm->filp);
if(cm->sects){
if(cm->flags & (1 << COW_VMALLOC_UPPER)) vfree(cm->sects);
else kfree(cm->sects);
}
if(cm) kfree(cm);
*cm_out = NULL;
return ret;
}
static int cow_init(struct snap_device *dev, const char *path, uint64_t elements, unsigned long sect_size, unsigned long cache_size, uint64_t file_max, const uint8_t *uuid, uint64_t seqid, struct cow_manager **cm_out){
int ret;
struct cow_manager *cm;
LOG_DEBUG("allocating cow manager, seqid = %llu", (unsigned long long)seqid);
cm = kzalloc(sizeof(struct cow_manager), GFP_KERNEL);
if(!cm){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating cow manager");
goto error;
}
LOG_DEBUG("creating cow file");
ret = file_open(path, O_CREAT | O_TRUNC, &cm->filp);
if(ret) goto error;
cm->version = COW_VERSION_CHANGED_BLOCKS;
cm->nr_changed_blocks = 0;
cm->flags = 0;
cm->allocated_sects = 0;
cm->file_max = file_max;
cm->sect_size = sect_size;
cm->seqid = seqid;
// Perhaps the code below needs a small explanation
//
// The COW file structure is as follows:
// +--------------------+----------+------+
// | HEADER (+padding) | SECTIONS | DATA |
// +--------------------+----------+------+
//
// +--------------------------------------+
// | SECTION |
// +--------------------------------------+
// | block 1 | block 2 | ... | block 4096 |
// +--------------------------------------+
//
// +--------------------------------------+
// | BLOCK |
// +--------------------------------------+
// | 0...4096 bytes |
// +--------------------------------------+
//
// 1. Each sector has 4096 cow block mappings
// 2. Each mapping describes 4096 bytes segment
//
// Suppose we have 256 MB disk.
// Taking PAGE_SIZE == COW_BLOCK_SIZE == 4096 bytes, yield:
//
// - 65536 blocks to be mapped;
// - We need 16 sectors to map everything
//
// Below we calculate that.
//
cm->log_sect_pages = get_order(sect_size * sizeof(uint64_t));
cm->total_sects = NUM_SEGMENTS(elements, cm->log_sect_pages + PAGE_SHIFT - 3);
cm->allowed_sects = __cow_calculate_allowed_sects(cache_size, cm->total_sects);
cm->data_offset = COW_HEADER_SIZE + (cm->total_sects * (sect_size * sizeof(uint64_t)));
cm->curr_pos = cm->data_offset / COW_BLOCK_SIZE;
cm->dev = dev;
if(uuid) memcpy(cm->uuid, uuid, COW_UUID_SIZE);
else generate_random_uuid(cm->uuid);
LOG_DEBUG("allocating cow manager array (%lu sections)", cm->total_sects);
cm->sects = kzalloc((cm->total_sects) * sizeof(struct cow_section), GFP_KERNEL | __GFP_NOWARN);
if(!cm->sects){
//try falling back to vmalloc
cm->flags |= (1 << COW_VMALLOC_UPPER);
cm->sects = vzalloc((cm->total_sects) * sizeof(struct cow_section));
if(!cm->sects){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating cow manager sects array");
goto error;
}
}
ret = file_allocate(cm, cm->filp, 0, file_max);
if(ret) goto error;
dev->sd_cow_inode = cm->filp->f_inode;
*cm_out = cm;
LOG_DEBUG("writing cow header");
ret = __cow_write_header_dirty(cm);
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error during cow manager initialization");
if(cm->filp) file_unlink_and_close(cm->filp);
if(cm->sects){
if(cm->flags & (1 << COW_VMALLOC_UPPER)) vfree(cm->sects);
else kfree(cm->sects);
}
if(cm) kfree(cm);
*cm_out = NULL;
return ret;
}
static int cow_truncate_to_index(struct cow_manager *cm){
//truncate the cow file to just the index
cm->flags |= (1 << COW_INDEX_ONLY);
return file_truncate(cm->filp, cm->data_offset);
}
static void cow_modify_cache_size(struct cow_manager *cm, unsigned long cache_size){
cm->allowed_sects = __cow_calculate_allowed_sects(cache_size, cm->total_sects);
}
static int cow_read_mapping(struct cow_manager *cm, uint64_t pos, uint64_t *out){
int ret;
uint64_t sect_idx = pos;
unsigned long sect_pos = do_div(sect_idx, cm->sect_size);
cm->sects[sect_idx].usage++;
if(!cm->sects[sect_idx].mappings){
if(!cm->sects[sect_idx].has_data){
*out = 0;
#ifdef NETLINK_DEBUG
nl_trace_event_cow(NL_EVENT_COW_READ_MAPPING, pos, *out);
#endif
return 0;
}else{
ret = __cow_load_section(cm, sect_idx);
if(ret) goto error;
}
}
*out = cm->sects[sect_idx].mappings[sect_pos];
#ifdef NETLINK_DEBUG
nl_trace_event_cow(NL_EVENT_COW_READ_MAPPING, pos, *out);
#endif
if(cm->allocated_sects > cm->allowed_sects){
ret = __cow_cleanup_mappings(cm);
if(ret) goto error;
}
return 0;
error:
LOG_ERROR(ret, "error reading cow mapping");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
return ret;
}
static int __cow_write_mapping(struct cow_manager *cm, uint64_t pos, uint64_t val){
int ret;
uint64_t sect_idx = pos;
unsigned long sect_pos = do_div(sect_idx, cm->sect_size);
cm->sects[sect_idx].usage++;
if(!cm->sects[sect_idx].mappings){
if(!cm->sects[sect_idx].has_data){
ret = __cow_alloc_section(cm, sect_idx, 1);
if(ret) goto error;
}else{
ret = __cow_load_section(cm, sect_idx);
if(ret) goto error;
}
}
if(cm->version >= COW_VERSION_CHANGED_BLOCKS && !cm->sects[sect_idx].mappings[sect_pos]) cm->nr_changed_blocks++;
#ifdef NETLINK_DEBUG
nl_trace_event_cow(NL_EVENT_COW_WRITE_MAPPING, pos, val);
#endif
cm->sects[sect_idx].mappings[sect_pos] = val;
if(cm->allocated_sects > cm->allowed_sects){
ret = __cow_cleanup_mappings(cm);
if(ret) goto error;
}
return 0;
error:
LOG_ERROR(ret, "error writing cow mapping");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
return ret;
}
#define __cow_write_current_mapping(cm, pos) __cow_write_mapping(cm, pos, (cm)->curr_pos)
#define cow_write_filler_mapping(cm, pos) __cow_write_mapping(cm, pos, 1)
static int __cow_write_data(struct cow_manager *cm, void *buf){
int ret = 0;
char *abs_path = NULL;
int abs_path_len;
uint64_t curr_size = cm->curr_pos * COW_BLOCK_SIZE;
if (snap_is_cancelled(cm)) return 0;
if(curr_size >= cm->file_max) {
snap_cancel(cm);
// ret = -EFBIG;
if (cm->filp)
file_get_absolute_pathname(cm->filp, &abs_path, &abs_path_len);
if(!abs_path){
LOG_ERROR(ret, "cow file max size exceeded (%llu/%llu), data will be cancelled.", curr_size, cm->file_max);
}else{
LOG_ERROR(ret, "cow file '%s' max size exceeded (%llu/%llu), data will be cancelled.", abs_path, curr_size, cm->file_max);
kfree(abs_path);
}
return 0;
}
#ifdef NETLINK_DEBUG
nl_trace_event_cow(NL_EVENT_COW_WRITE_DATA, 0, 0);
#endif
ret = file_write(cm, buf, curr_size, COW_BLOCK_SIZE);
if(ret) goto error;
cm->curr_pos++;
return 0;
error:
LOG_ERROR(ret, "error writing cow data");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
return ret;
}
static int cow_write_current(struct cow_manager *cm, uint64_t block, void *buf){
int ret;
uint64_t block_mapping;
//read this mapping from the cow manager
ret = cow_read_mapping(cm, block, &block_mapping);
if(ret) goto error;
//if the block mapping already exists return so we don't overwrite it
if(block_mapping) return 0;
//write the mapping
ret = __cow_write_current_mapping(cm, block);
if(ret) goto error;
//write the data
ret = __cow_write_data(cm, buf);
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error writing cow data and mapping");
return ret;
}
static int cow_read_data(struct cow_manager *cm, void *out_buf, uint64_t block_pos, unsigned long block_off, unsigned long len){
int ret;
char *read_buf = kzalloc(COW_BLOCK_SIZE, GFP_KERNEL);
#ifdef NETLINK_DEBUG
nl_trace_event_cow(NL_EVENT_COW_READ_DATA, 0, 0);
#endif
if(block_off >= COW_BLOCK_SIZE) return -EINVAL;
ret = file_read(cm, read_buf, (block_pos * COW_BLOCK_SIZE), COW_BLOCK_SIZE);
if(ret){
LOG_ERROR(ret, "error reading cow data");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
kfree(read_buf);
return ret;
}
memcpy(out_buf, read_buf + block_off, len);
kfree(read_buf);
return 0;
}
/***************************SECTOR_SET LIST FUNCTIONS**************************/
static inline void sset_list_init(struct sset_list *sl){
sl->head = sl->tail = NULL;
}
static inline int sset_list_empty(const struct sset_list *sl){
return sl->head == NULL;
}
static void sset_list_add(struct sset_list *sl, struct sector_set *sset){
sset->next = NULL;
if(sl->tail) sl->tail->next = sset;
else sl->head = sset;
sl->tail = sset;
}
static struct sector_set *sset_list_pop(struct sset_list *sl){
struct sector_set *sset = sl->head;
if(sset) {
sl->head = sl->head->next;
if(!sl->head) sl->tail = NULL;
sset->next = NULL;
}
return sset;
}
/****************************BIO QUEUE FUNCTIONS****************************/
static void bio_queue_init(struct bio_queue *bq){
bio_list_init(&bq->bios);
spin_lock_init(&bq->lock);
init_waitqueue_head(&bq->event);
}
static int bio_queue_empty(const struct bio_queue *bq){
return bio_list_empty(&bq->bios);
}
static void bio_queue_add(struct bio_queue *bq, struct bio *bio){
unsigned long flags;
spin_lock_irqsave(&bq->lock, flags);
bio_list_add(&bq->bios, bio);
spin_unlock_irqrestore(&bq->lock, flags);
wake_up(&bq->event);
}
static struct bio *bio_queue_dequeue(struct bio_queue *bq){
unsigned long flags;
struct bio *bio;
spin_lock_irqsave(&bq->lock, flags);
bio = bio_list_pop(&bq->bios);
spin_unlock_irqrestore(&bq->lock, flags);
return bio;
}
static int bio_overlap(const struct bio *bio1, const struct bio *bio2){
return max(bio_sector(bio1), bio_sector(bio2)) <= min(bio_sector(bio1) + (bio_size(bio1) / SECTOR_SIZE), bio_sector(bio2) + (bio_size(bio2) / SECTOR_SIZE));
}
static struct bio *bio_queue_dequeue_delay_read(struct bio_queue *bq){
unsigned long flags;
struct bio *bio, *tmp, *prev = NULL;
spin_lock_irqsave(&bq->lock, flags);
bio = bio_list_pop(&bq->bios);
if(!bio_data_dir(bio)){
bio_list_for_each(tmp, &bq->bios){
if(bio_data_dir(tmp) && bio_overlap(bio, tmp)){
if(prev) prev->bi_next = bio;
else bq->bios.head = bio;
if(bq->bios.tail == tmp) bq->bios.tail = bio;
bio->bi_next = tmp->bi_next;
tmp->bi_next = NULL;
bio = tmp;
goto out;
}
prev = tmp;
}
}
out:
spin_unlock_irqrestore(&bq->lock, flags);
return bio;
}
/****************************SSET QUEUE FUNCTIONS****************************/
static void sset_queue_init(struct sset_queue *sq){
sset_list_init(&sq->ssets);
spin_lock_init(&sq->lock);
init_waitqueue_head(&sq->event);
}
static int sset_queue_empty(const struct sset_queue *sq){
return sset_list_empty(&sq->ssets);
}
static void sset_queue_add(struct sset_queue *sq, struct sector_set *sset){
unsigned long flags;
spin_lock_irqsave(&sq->lock, flags);
sset_list_add(&sq->ssets, sset);
spin_unlock_irqrestore(&sq->lock, flags);
wake_up(&sq->event);
}
static struct sector_set *sset_queue_dequeue(struct sset_queue *sq){
unsigned long flags;
struct sector_set *sset;
spin_lock_irqsave(&sq->lock, flags);
sset = sset_list_pop(&sq->ssets);
spin_unlock_irqrestore(&sq->lock, flags);
return sset;
}
/***************************TRACING PARAMS FUNCTIONS**************************/
static int tp_alloc(struct snap_device *dev, struct bio *bio, struct tracing_params **tp_out){
struct tracing_params *tp;
tp = kzalloc(1 * sizeof(struct tracing_params), GFP_NOIO);
if(!tp){
LOG_ERROR(-ENOMEM, "error allocating tracing parameters struct");
*tp_out = tp;
return -ENOMEM;
}
tp->dev = dev;
tp->orig_bio = bio;
tp->bio_sects.head = NULL;
tp->bio_sects.tail = NULL;
atomic_set(&tp->refs, 1);
*tp_out = tp;
return 0;
}
static void tp_get(struct tracing_params *tp){
atomic_inc(&tp->refs);
}
static void tp_put(struct tracing_params *tp){
//drop a reference to the tp
if(atomic_dec_and_test(&tp->refs)){
struct bio_sector_map *next, *curr = NULL;
//if there are no references left, its safe to release the orig_bio
bio_queue_add(&tp->dev->sd_orig_bios, tp->orig_bio);
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_RELEASED, tp->orig_bio, 0);
#endif
// free nodes in the sector map list
for (curr = tp->bio_sects.head; curr != NULL; curr = next)
{
next = curr->next;
kfree(curr);
}
kfree(tp);
}
}
static int tp_add(struct tracing_params* tp, struct bio* bio) {
struct bio_sector_map* map;
map = kzalloc(1 * sizeof(struct bio_sector_map), GFP_NOIO);
if (!map) {
LOG_ERROR(-ENOMEM, "error allocating new bio_sector_map struct");
return -ENOMEM;
}
map->bio = bio;
map->sect = bio_sector(bio);
map->size = bio_size(bio);
map->next = NULL;
if (tp->bio_sects.head == NULL) {
tp->bio_sects.head = map;
tp->bio_sects.tail = map;
}
else {
tp->bio_sects.tail->next = map;
tp->bio_sects.tail = map;
}
return 0;
}
/***************************TRACING OPS FUNCTIONS**************************/
#ifdef USE_BDOPS_SUBMIT_BIO
static MRF_RETURN_TYPE tracing_mrf(struct bio *);
static int tracing_ops_alloc(struct snap_device *dev) {
struct tracing_ops *trops;
trops = kmalloc(sizeof(struct tracing_ops), GFP_KERNEL);
if(!trops) {
LOG_ERROR(-ENOMEM, "error allocating tracing ops struct");
return -ENOMEM;
}
trops->bd_ops = kmalloc(sizeof(struct block_device_operations), GFP_KERNEL);
if(!trops->bd_ops) {
kfree(trops);
LOG_ERROR(-ENOMEM, "error allocating block device ops struct");
return -ENOMEM;
}
memcpy(trops->bd_ops, elastio_snap_get_bd_ops(dev->sd_base_dev), sizeof(struct block_device_operations));
// Set tracing_mrf as submit_bio. All other content is already there copied from the original structure.
#ifdef HAVE_BD_HAS_SUBMIT_BIO
trops->has_submit_bio = dev->sd_base_dev->bd_has_submit_bio;
#elif defined HAVE_BD_HAS_SUBMIT_BIO_FLAGS
trops->has_submit_bio = bdev_test_flag(dev->sd_base_dev, BD_HAS_SUBMIT_BIO);
#endif
trops->bd_ops->submit_bio = tracing_mrf;
atomic_set(&trops->refs, 1);
dev->sd_tracing_ops = trops;
return 0;
}
static inline struct tracing_ops* tracing_ops_get(struct tracing_ops *trops) {
if (trops) atomic_inc(&trops->refs);
return trops;
}
// Multiple devices on the same disk are sharing block_device_operations structure.
// This struct is replaced with the value of sd_tracing_ops in case if one of the partitions is tracked by this driver.
// We should not free this struct here unless no other devices are tracked.
static inline void tracing_ops_put(struct tracing_ops *trops) {
//drop a reference to the tracing ops
if(atomic_dec_and_test(&trops->refs)) {
kfree(trops->bd_ops);
kfree(trops);
}
}
#endif
/****************************BIO HELPER FUNCTIONS*****************************/
static inline struct inode *page_get_inode(struct page *pg){
if(!pg) return NULL;
// page_mapping() was not exported until 4.8, use compound_head() instead
#ifdef HAVE_COMPOUND_HEAD
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(2.6.22)
pg = compound_head(pg);
#endif
if(PageAnon(pg)) return NULL;
if(!pg->mapping) return NULL;
if (!virt_addr_valid(pg->mapping)) return NULL;
return pg->mapping->host;
}
static int bio_needs_cow(struct bio *bio, struct snap_device *dev){
bio_iter_t iter;
bio_iter_bvec_t bvec;
if (!test_bit(COW_ON_BDEV, &dev->sd_cow_state)) {
return 1;
}
#if (defined HAVE_ENUM_REQ_OPF) || \
(defined HAVE_ENUM_REQ_OP && defined HAVE_WRITE_ZEROES)
// HAVE_ENUM_REQ_OPF: KERNEL_VERSION >= 4.10 && KERNEL_VERSION <= 5.19
// HAVE_ENUM_REQ_OP: KERNEL_VERSION < 4.10 && KERNEL_VERSION >= 6.0
// HAVE_WRITE_ZEROES: KERNEL_VERSION >= 4.10
if(bio_op(bio) == REQ_OP_WRITE_ZEROES) return 1;
#endif
//check the inode of each page return true if it does not match our cow file
bio_for_each_segment(bvec, bio, iter){
if(page_get_inode(bio_iter_page(bio, iter)) != dev->sd_cow_inode) {
return 1;
}
}
return 0;
}
#ifndef HAVE_BIO_BI_POOL
static void bio_destructor_tp(struct bio *bio){
struct tracing_params *tp = bio->bi_private;
bio_free(bio, dev_bioset(tp->dev));
}
static void bio_destructor_snap_dev(struct bio *bio){
struct snap_device *dev = bio->bi_private;
bio_free(bio, dev_bioset(dev));
}
#endif
static void bio_free_clone(struct bio *bio){
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_FREE, bio, 0);
#endif
bio_free_pages(bio);
bio_put(bio);
}
static int bio_make_read_clone(struct block_device *bdev, struct bio_set *bs, struct tracing_params *tp, struct bio *orig_bio, sector_t sect, unsigned int pages, struct bio **bio_out, unsigned int *bytes_added){
int ret;
struct bio *new_bio;
struct page *pg;
unsigned int i, bytes, total = 0, actual_pages = (pages > BIO_MAX_PAGES)? BIO_MAX_PAGES : pages;
//allocate bio clone
#ifdef HAVE_BIO_ALLOC_BIOSET_5
new_bio = bio_alloc_bioset(bdev, actual_pages, orig_bio->bi_opf, GFP_NOIO, bs);
#else
new_bio = bio_alloc_bioset(GFP_NOIO, actual_pages, bs);
#endif
if(!new_bio){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating bio clone - bs = %p, pages = %u", bs, pages);
goto error;
}
#ifndef HAVE_BIO_BI_POOL
new_bio->bi_destructor = bio_destructor_tp;
#endif
//populate read bio
tp_get(tp);
new_bio->bi_private = tp;
new_bio->bi_end_io = on_bio_read_complete;
elastio_snap_bio_copy_dev(new_bio, orig_bio);
elastio_snap_set_bio_ops(new_bio, REQ_OP_READ, REQ_SYNC);
bio_sector(new_bio) = sect;
bio_idx(new_bio) = 0;
/*
* The following flags were added
* in v4.10 and in v5.12 respectively
* and may affect bio processing sequence.
* For this reason, we copy them from the
* original bio
*/
#if defined HAVE_BIO_REMAPPED
if (bio_flagged(orig_bio, BIO_REMAPPED))
bio_set_flag(new_bio, BIO_REMAPPED);
#endif
#if defined HAVE_BIO_THROTTLED
if (bio_flagged(orig_bio, BIO_THROTTLED))
bio_set_flag(new_bio, BIO_THROTTLED);
#endif
//fill the bio with pages
for(i = 0; i < actual_pages; i++){
//allocate a page and add it to our bio
pg = alloc_page(GFP_NOIO);
if(!pg){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating read bio page %u", i);
goto error;
}
//add the page to the bio
bytes = bio_add_page(new_bio, pg, PAGE_SIZE, 0);
if(bytes != PAGE_SIZE){
__free_page(pg);
break;
}
total += bytes;
}
*bytes_added = total;
*bio_out = new_bio;
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_CLONED, new_bio, 0);
#endif
return 0;
error:
if(ret) LOG_ERROR(ret, "error creating read clone of write bio");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
if(new_bio) bio_free_clone(new_bio);
*bytes_added = 0;
*bio_out = NULL;
return ret;
}
/*******************BIO / SECTOR_SET PROCESSING LOGIC***********************/
static int snap_read_bio_get_mode(const struct snap_device *dev, struct bio *bio, int *mode){
int ret, start_mode = 0;
bio_iter_t iter;
bio_iter_bvec_t bvec;
unsigned int bytes;
uint64_t block_mapping, curr_byte, curr_end_byte = bio_sector(bio) * SECTOR_SIZE;
bio_for_each_segment(bvec, bio, iter){
//reset the number of bytes we have traversed for this bio_vec
bytes = 0;
//while we still have data left to be written into the page
while(bytes < bio_iter_len(bio, iter)){
//find the start and stop byte for our next write
curr_byte = curr_end_byte;
curr_end_byte += min(COW_BLOCK_SIZE - (curr_byte % COW_BLOCK_SIZE), ((uint64_t)bio_iter_len(bio, iter) - bytes));
//check if the mapping exists
ret = cow_read_mapping(dev->sd_cow, curr_byte / COW_BLOCK_SIZE, &block_mapping);
if(ret) goto error;
if(!start_mode && block_mapping) start_mode = READ_MODE_COW_FILE;
else if(!start_mode && !block_mapping) start_mode = READ_MODE_BASE_DEVICE;
else if((start_mode == READ_MODE_COW_FILE && !block_mapping) || (start_mode == READ_MODE_BASE_DEVICE && block_mapping)){
*mode = READ_MODE_MIXED;
return 0;
}
//increment the number of bytes we have written
bytes += curr_end_byte - curr_byte;
}
}
*mode = start_mode;
return 0;
error:
LOG_ERROR(ret, "error finding read mode");
return ret;
}
static int snap_handle_read_bio(const struct snap_device *dev, struct bio *bio){
int ret, mode;
struct bio_vec *bvec;
#ifdef HAVE_BVEC_ITER_ALL
struct bvec_iter_all iter;
#else
int i = 0;
#endif
void *orig_private;
bio_end_io_t *orig_end_io;
char *data;
sector_t bio_orig_sect, cur_block, cur_sect;
unsigned int bio_orig_idx, bio_orig_size;
uint64_t block_mapping, bytes_to_copy, block_off, bvec_off;
//save the original state of the bio
orig_private = bio->bi_private;
orig_end_io = bio->bi_end_io;
bio_orig_idx = bio_idx(bio);
bio_orig_size = bio_size(bio);
bio_orig_sect = bio_sector(bio);
elastio_snap_bio_set_dev(bio, dev->sd_base_dev);
elastio_snap_set_bio_ops(bio, REQ_OP_READ, REQ_SYNC);
//detect fastpath for bios completely contained within either the cow file or the base device
ret = snap_read_bio_get_mode(dev, bio, &mode);
if(ret) goto out;
//submit the bio to the base device and wait for completion
if(mode != READ_MODE_COW_FILE){
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_HANDLE_READ_BASE, bio, 0);
#endif
ret = elastio_snap_submit_bio_wait(bio);
if(ret){
LOG_ERROR(ret, "error reading from base device for read");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
goto out;
}
#ifdef HAVE_BIO_BI_REMAINING
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0)
atomic_inc(&bio->bi_remaining);
#endif
}
if(mode != READ_MODE_BASE_DEVICE){
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_HANDLE_READ_COW, bio, 0);
#endif
//reset the bio
bio_idx(bio) = bio_orig_idx;
bio_size(bio) = bio_orig_size;
bio_sector(bio) = bio_orig_sect;
cur_sect = bio_sector(bio);
//iterate over all the segments and fill the bio. this more complex than writing since we don't have the block aligned guarantee
#ifdef HAVE_BVEC_ITER_ALL
bio_for_each_segment_all(bvec, bio, iter) {
#else
bio_for_each_segment_all(bvec, bio, i) {
#endif
//map the page into kernel space
data = kmap(bvec->bv_page);
cur_block = (cur_sect * SECTOR_SIZE) / COW_BLOCK_SIZE;
block_off = (cur_sect * SECTOR_SIZE) % COW_BLOCK_SIZE;
bvec_off = bvec->bv_offset;
while(bvec_off < bvec->bv_offset + bvec->bv_len){
bytes_to_copy = min(bvec->bv_offset + bvec->bv_len - bvec_off, COW_BLOCK_SIZE - block_off);
//check if the mapping exists
ret = cow_read_mapping(dev->sd_cow, cur_block, &block_mapping);
if(ret){
kunmap(bvec->bv_page);
goto out;
}
//if the mapping exists, read it into the page, overwriting the live data
if(block_mapping){
ret = cow_read_data(dev->sd_cow, data + bvec_off, block_mapping, block_off, bytes_to_copy);
if(ret){
kunmap(bvec->bv_page);
goto out;
}
}
cur_sect += bytes_to_copy / SECTOR_SIZE;
cur_block = (cur_sect * SECTOR_SIZE) / COW_BLOCK_SIZE;
block_off = (cur_sect * SECTOR_SIZE) % COW_BLOCK_SIZE;
bvec_off += bytes_to_copy;
}
//unmap the page from kernel space
kunmap(bvec->bv_page);
}
}
out:
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_HANDLE_READ_DONE, bio, 0);
#endif
if(ret) {
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
LOG_ERROR(ret, "error handling read bio");
bio_idx(bio) = bio_orig_idx;
bio_size(bio) = bio_orig_size;
bio_sector(bio) = bio_orig_sect;
}
//revert bio's original data
bio->bi_private = orig_private;
bio->bi_end_io = orig_end_io;
return ret;
}
static int snap_handle_write_bio(const struct snap_device *dev, struct bio *bio){
int ret;
char *data;
sector_t start_block, end_block = SECTOR_TO_BLOCK(bio_sector(bio));
#ifdef HAVE_BVEC_ITER_ALL
struct bvec_iter_all iter;
struct bio_vec *bvec;
#else
int i = 0;
struct bio_vec *bvec;
#endif
/*
* Previously we iterated using bio_for_each_segment(), which
* caused problems in case if our bio was split by the system.
* It is replaced with bio_for_each_segment_all() as we own the
* bio and can guarantee that we have access to its bvecs
*/
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_HANDLE_WRITE, bio, 0);
#endif
#ifdef HAVE_BVEC_ITER_ALL
//iterate through the bio and handle each segment (which is guaranteed to be block aligned)
bio_for_each_segment_all(bvec, bio, iter) {
#else
bio_for_each_segment_all(bvec, bio, i) {
#endif
//find the start and end block
start_block = end_block;
end_block = start_block + (bvec->bv_len / COW_BLOCK_SIZE);
//map the page into kernel space
data = kmap(bvec->bv_page);
//loop through the blocks in the page
for(; start_block < end_block; start_block++){
//pass the block to the cow manager to be handled
ret = cow_write_current(dev->sd_cow, start_block, data);
if(ret){
kunmap(bvec->bv_page);
goto error;
}
}
//unmap the page
kunmap(bvec->bv_page);
}
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_HANDLE_WRITE_DONE, bio, 0);
#endif
return 0;
error:
LOG_ERROR(ret, "error handling write bio");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
return ret;
}
static int inc_handle_sset(const struct snap_device *dev, struct sector_set *sset){
int ret;
sector_t start_block = SECTOR_TO_BLOCK(sset->sect);
sector_t end_block = NUM_SEGMENTS(sset->sect + sset->len, COW_BLOCK_LOG_SIZE - SECTOR_SHIFT);
for(; start_block < end_block; start_block++){
ret = cow_write_filler_mapping(dev->sd_cow, start_block);
if(ret) goto error;
}
return 0;
error:
LOG_ERROR(ret, "error handling sset");
return ret;
}
static int snap_mrf_thread(void *data){
int ret;
struct snap_device *dev = data;
struct bio_queue *bq = &dev->sd_orig_bios;
struct bio *bio;
MAYBE_UNUSED(ret);
//give this thread the highest priority we are allowed
set_user_nice(current, MIN_NICE);
while(!kthread_should_stop() || !bio_queue_empty(bq)) {
//wait for a bio to process or a kthread_stop call
wait_event_interruptible(bq->event, kthread_should_stop() || !bio_queue_empty(bq));
if(bio_queue_empty(bq)) continue;
//safely dequeue a bio
bio = bio_queue_dequeue(bq);
//submit the original bio to the block IO layer
elastio_snap_bio_op_set_flag(bio, ELASTIO_SNAP_PASSTHROUGH);
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_CALL_ORIG, bio, 0);
#endif
ret = elastio_snap_call_mrf(dev->sd_orig_mrf, bio);
#ifdef HAVE_MAKE_REQUEST_FN_INT
if(ret) generic_make_request(bio);
#endif
}
return 0;
}
static int snap_cow_thread(void *data){
int ret, is_failed = 0;
struct snap_device *dev = data;
struct bio_queue *bq = &dev->sd_cow_bios;
struct bio *bio;
//give this thread the highest priority we are allowed
set_user_nice(current, MIN_NICE);
while(!kthread_should_stop() || !bio_queue_empty(bq) || atomic64_read(&dev->sd_submitted_cnt) != atomic64_read(&dev->sd_received_cnt)) {
//wait for a bio to process or a kthread_stop call
wait_event_interruptible(bq->event, kthread_should_stop() || !bio_queue_empty(bq));
/*
/ We should pertend that the snapshot device is alive and don't give EIO on read even if it's in the failed state,
/ in case, if dev->sd_ignore_snap_errors == true. This behavior is needed for the userspace apps to be not killed
/ by SIGBUS if they are using a the snapshot device as a memmap'd file. For this purpose, cow_free_members shouldn't
/ be called here. But it should be for the regular snapshot device readers.
*/
if(!is_failed && tracer_read_fail_state(dev)){
LOG_DEBUG("error detected in cow thread, cleaning up cow");
is_failed = 1;
if(dev->sd_cow) cow_free_members(dev->sd_cow);
}
if(bio_queue_empty(bq)) continue;
//safely dequeue a bio
bio = bio_queue_dequeue_delay_read(bq);
//pass bio to handler
if(!bio_data_dir(bio)){
//if we're in the fail state just send back an IO error and free the bio
if(is_failed){
elastio_snap_bio_endio(bio, wrap_err_io(dev)); //end the bio with an IO error
continue;
}
if (dev->sd_cow && snap_is_cancelled(dev->sd_cow)) {
LOG_DEBUG("cancelling read bio: sect = %llu size = %u", (unsigned long long)bio_sector(bio), bio_size(bio) / 512);
elastio_snap_bio_endio(bio, -EFBIG);
continue;
}
ret = snap_handle_read_bio(dev, bio);
if(ret){
LOG_ERROR(ret, "error handling read bio in kernel thread");
tracer_set_fail_state(dev, ret);
}
elastio_snap_bio_endio(bio, (ret)? wrap_err_io(dev) : 0);
}else{
if(is_failed){
atomic64_inc(&dev->sd_processed_cnt);
bio_free_clone(bio);
continue;
}
// Handle write bio in all cases except just when an error have to be ignored and the snapshot is in the error state.
// NOTE: We can't rely on 'is_failed' value already. The actual error state might have already changed while the BIO was dequeued...
if (!dev->sd_ignore_snap_errors || tracer_read_fail_state(dev) == 0)
{
ret = snap_handle_write_bio(dev, bio);
if (ret) {
LOG_ERROR(ret, "error handling write bio in kernel thread");
tracer_set_fail_state(dev, ret);
}
}
atomic64_inc(&dev->sd_processed_cnt);
bio_free_clone(bio);
}
}
LOG_DEBUG("snap_cow_thread() done.");
return 0;
}
static int inc_sset_thread(void *data){
int ret, is_failed = 0;
struct snap_device *dev = data;
struct sset_queue *sq = &dev->sd_pending_ssets;
struct sector_set *sset;
//give this thread the highest priority we are allowed
set_user_nice(current, MIN_NICE);
while(!kthread_should_stop() || !sset_queue_empty(sq)) {
//wait for a sset to process or a kthread_stop call
wait_event_interruptible(sq->event, kthread_should_stop() || !sset_queue_empty(sq));
if(!is_failed && tracer_read_fail_state(dev)){
LOG_DEBUG("error detected in sset thread, cleaning up cow");
is_failed = 1;
if(dev->sd_cow) cow_free_members(dev->sd_cow);
}
if(sset_queue_empty(sq)) continue;
//safely dequeue a sset
sset = sset_queue_dequeue(sq);
//if there has been a problem don't process any more, just free the ones we have
if(is_failed){
kfree(sset);
continue;
}
//pass the sset to the handler
ret = inc_handle_sset(dev, sset);
if(ret){
LOG_ERROR(ret, "error handling sector set in kernel thread");
tracer_set_fail_state(dev, ret);
}
//free the sector set
kfree(sset);
}
LOG_DEBUG("inc_sset_thread() done.");
return 0;
}
/****************************BIO TRACING LOGIC*****************************/
static void __on_bio_read_complete(struct bio *bio, int err){
int ret;
struct tracing_params *tp = bio->bi_private;
struct snap_device *dev = tp->dev;
struct bio_sector_map* map = NULL;
#ifndef HAVE_BVEC_ITER
unsigned short i = 0;
#endif
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_READ_COMPLETE, bio, 0);
#endif
//check for read errors
if(err){
ret = err;
LOG_ERROR(ret, "error reading from base device for copy on write");
goto error;
}
//change the bio into a write bio
elastio_snap_set_bio_ops(bio, REQ_OP_WRITE, 0);
//reset the bio iterator to its original state
for(map = tp->bio_sects.head; map != NULL && map->bio != NULL; map = map->next) {
if(bio == map->bio){
bio_sector(bio) = map->sect - dev->sd_sect_off;
bio_idx(bio) = 0;
break;
}
}
/*
* Reset the position in each bvec. Unnecessary with bvec iterators. Will cause multipage bvec capable kernels to
* lock up.
*/
#ifndef HAVE_BVEC_ITER
//#if LINUX_VERSION_CODE < KERNEL_VERSION(3,14,0)
for(i = 0; i < bio->bi_vcnt; i++){
bio->bi_io_vec[i].bv_len = PAGE_SIZE;
bio->bi_io_vec[i].bv_offset = 0;
}
#endif
/*
* drop our reference to the tp (will queue the orig_bio if nobody else is using it)
* at this point we set bi_private to the snap_device and change the destructor to use
* that instead. This only matters on older kernels
*/
bio->bi_private = dev;
#ifndef HAVE_BIO_BI_POOL
bio->bi_destructor = bio_destructor_snap_dev;
#endif
//queue cow bio for processing by kernel thread
bio_queue_add(&dev->sd_cow_bios, bio);
atomic64_inc(&dev->sd_received_cnt);
smp_wmb();
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_QUEUED, bio, 0);
#endif
tp_put(tp);
return;
error:
LOG_ERROR(ret, "error during bio read complete callback");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
tracer_set_fail_state(dev, ret);
tp_put(tp);
bio_free_clone(bio);
}
static inline bool elastio_snap_request_queue_stopped(struct request_queue *q)
{
struct snap_device *dev = q->queuedata;
return atomic_read(&dev->sd_ignore_requests);
}
static void elastio_snap_stop_request_queue(struct request_queue *q)
{
unsigned long flags;
struct snap_device *dev;
MAYBE_UNUSED(flags);
/* The request queue will not exist
* in the incremental mode
*/
if (!q)
return;
dev = q->queuedata;
if (!dev) {
LOG_ERROR(-ENODEV, "no device found, skip request queue stop");
return;
}
atomic_set(&dev->sd_ignore_requests, 1);
#ifdef HAVE_BLK_STOP_QUEUE
spin_lock_irqsave(q->queue_lock, flags);
blk_stop_queue(q);
spin_unlock_irqrestore(q->queue_lock, flags);
#else
blk_mq_stop_hw_queues(q);
#endif
}
/** Resolves issue https://github.com/elastio/elastio-snap/issues/170 */
static inline void wait_for_bio_complete(struct snap_device *dev)
{
struct bio_queue *bq = &dev->sd_cow_bios;
if (!wait_event_interruptible_timeout(bq->event,
atomic64_read(&dev->sd_submitted_cnt) == atomic64_read(&dev->sd_processed_cnt),
msecs_to_jiffies(WAIT_SUBMITTED_BIOS_MSEC))) {
LOG_WARN("failed wait for all submitted BIOs to be processed after %d ms. bio submitted = %lld, bio processed = %lld",
WAIT_SUBMITTED_BIOS_MSEC, (u64)atomic64_read(&dev->sd_submitted_cnt), (u64)atomic64_read(&dev->sd_processed_cnt));
}
}
#ifdef HAVE_BIO_ENDIO_INT
static int on_bio_read_complete(struct bio *bio, unsigned int bytes, int err){
if(bio->bi_size) return 1;
__on_bio_read_complete(bio, err);
return 0;
}
#elif !defined HAVE_BIO_ENDIO_1
static void on_bio_read_complete(struct bio *bio, int err){
if(!test_bit(BIO_UPTODATE, &bio->bi_flags)) err = -EIO;
__on_bio_read_complete(bio, err);
}
#elif defined HAVE_BLK_STATUS_T
static void on_bio_read_complete(struct bio *bio){
__on_bio_read_complete(bio, blk_status_to_errno(bio->bi_status));
}
#else
static void on_bio_read_complete(struct bio *bio){
__on_bio_read_complete(bio, bio->bi_error);
}
#endif
static int memory_is_too_low(struct snap_device *dev) {
int ret;
struct sysinfo si;
static __kernel_ulong_t totalram = 0;
if (totalram == 0) {
si_meminfo(&si);
totalram = si.totalram;
}
ret = tracer_read_fail_state(dev);
if (ret != -ENOMEM && ((si_mem_available() * 100) / totalram) < LOW_MEMORY_FAIL_PERCENT) {
LOG_WARN("physical memory usage has exceeded %d%% threshold. cow file update is stopped", (100 - LOW_MEMORY_FAIL_PERCENT));
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
ret = -ENOMEM;
tracer_set_fail_state(dev, ret);
}
return ret;
}
static int snap_trace_bio(struct snap_device *dev, struct bio *bio){
int ret;
struct bio *new_bio = NULL;
struct tracing_params *tp = NULL;
sector_t start_sect, end_sect;
unsigned int bytes, pages;
int max_sectors;
#if defined HAVE_ENUM_REQ_OPF || defined HAVE_ENUM_REQ_OP
unsigned int bio_op_idx = 0;
#endif
//if we don't need to cow this bio or if the snapshot is in the failed state,
//e.g. physical memory usage has exceeded threshold or COW file state is failed,
//just call the real mrf normally
if (!bio_needs_cow(bio, dev) || memory_is_too_low(dev) || tracer_read_fail_state(dev)) {
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_CALL_ORIG, bio, 0);
#endif
return elastio_snap_call_mrf(dev->sd_orig_mrf, bio);
}
max_sectors = queue_max_sectors(bdev_get_queue(dev->sd_base_dev));
if (bio_sectors(bio) > max_sectors) {
#ifdef HAVE_BIO_SPLIT_4
struct bio *split = bio_split(bio, max_sectors,
GFP_NOIO, dev_bioset(dev));
bio_chain(split, bio);
#ifdef HAVE_GENERIC_MAKE_REQUEST
generic_make_request(bio);
#else
submit_bio_noacct(bio);
#endif
bio = split;
#else
struct bio_pair *bp = bio_split(bio, max_sectors);
snap_trace_bio(dev, &bp->bio1);
snap_trace_bio(dev, &bp->bio2);
bio_pair_release(bp);
return 0;
#endif
}
#if defined HAVE_ENUM_REQ_OPF || defined HAVE_ENUM_REQ_OP
bio_op_idx = bio_op(bio);
if (likely(bio_op_idx < BIO_STATS_MAX_ELEMENTS)) {
dev->sd_bio_stats_traced[bio_op_idx]++;
}
#endif
//the cow manager works in 4096 byte blocks, so read clones must also be 4096 byte aligned
start_sect = ROUND_DOWN(bio_sector(bio) - dev->sd_sect_off, SECTORS_PER_BLOCK) + dev->sd_sect_off;
end_sect = ROUND_UP(bio_sector(bio) + (bio_size(bio) / SECTOR_SIZE) - dev->sd_sect_off, SECTORS_PER_BLOCK) + dev->sd_sect_off;
pages = (end_sect - start_sect) / SECTORS_PER_PAGE;
//allocate tracing_params struct to hold all pointers we will need across contexts
ret = tp_alloc(dev, bio, &tp);
if(ret) goto error;
retry:
//allocate and populate read bio clone. This bio may not have all the pages we need due to queue restrictions
ret = bio_make_read_clone(dev->sd_base_dev, dev_bioset(dev), tp, bio, start_sect, pages, &new_bio, &bytes);
if(ret) goto error;
//set pointers for read clone
ret = tp_add(tp, new_bio);
if (ret) goto error;
atomic64_inc(&dev->sd_submitted_cnt);
smp_wmb();
//
// submit the bios
//
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_CALL_ORIG, new_bio, 0);
#endif
#ifdef USE_BDOPS_SUBMIT_BIO
// send bio by calling original mrf when its present or call an ordinal submit_bio instead
if (dev->sd_orig_mrf) {
elastio_snap_call_mrf(dev->sd_orig_mrf, new_bio);
} else {
elastio_snap_submit_bio(new_bio);
}
#else
elastio_snap_submit_bio(new_bio);
#endif
//if our bio didn't cover the entire clone we must keep creating bios until we have
if(bytes / PAGE_SIZE < pages){
start_sect += bytes / SECTOR_SIZE;
pages -= bytes / PAGE_SIZE;
goto retry;
}
//drop our reference to the tp
tp_put(tp);
return 0;
error:
LOG_ERROR(ret, "error tracing bio for snapshot");
tracer_set_fail_state(dev, ret);
//clean up the bio we allocated (but did not submit)
if(new_bio) bio_free_clone(new_bio);
if(tp) tp_put(tp);
//this function only returns non-zero if the real mrf does not. Errors set the fail state.
return 0;
}
static int inc_make_sset(struct snap_device *dev, sector_t sect, unsigned int len){
struct sector_set *sset;
//allocate sector set to hold record of change sectors
sset = kmalloc(sizeof(struct sector_set), GFP_NOIO);
if(!sset){
LOG_ERROR(-ENOMEM, "error allocating sector set");
return -ENOMEM;
}
sset->sect = sect - dev->sd_sect_off;
sset->len = len;
//queue sset for processing by kernel thread
sset_queue_add(&dev->sd_pending_ssets, sset);
return 0;
}
static int inc_trace_bio(struct snap_device *dev, struct bio *bio){
int ret = 0, is_initialized = 0;
sector_t start_sect = 0, end_sect = bio_sector(bio);
bio_iter_t iter;
bio_iter_bvec_t bvec;
#if defined HAVE_ENUM_REQ_OPF || defined HAVE_ENUM_REQ_OP
unsigned int bio_op_idx = 0;
#endif
#if defined HAVE_ENUM_REQ_OPF || defined HAVE_ENUM_REQ_OP
bio_op_idx = bio_op(bio);
if (likely(bio_op_idx < BIO_STATS_MAX_ELEMENTS)) {
dev->sd_bio_stats_traced[bio_op_idx]++;
}
#endif
if (!test_bit(COW_ON_BDEV, &dev->sd_cow_state)){
// if the cow is non-resident, then we don't need to check if
// the bio is for the cow file.
ret = inc_make_sset(dev, bio_sector(bio), bio_size(bio) / SECTOR_SIZE);
goto out;
}
#if (defined HAVE_ENUM_REQ_OPF) || \
(defined HAVE_ENUM_REQ_OP && defined HAVE_WRITE_ZEROES)
// HAVE_ENUM_REQ_OPF: KERNEL_VERSION >= 4.10 && KERNEL_VERSION <= 5.19
// HAVE_ENUM_REQ_OP: KERNEL_VERSION <= 4.9 && KERNEL_VERSION >= 6.0
// HAVE_WRITE_ZEROES: KERNEL_VERSION >= 4.10
if(bio_op(bio) == REQ_OP_WRITE_ZEROES){
ret = inc_make_sset(dev, bio_sector(bio), bio_size(bio) / SECTOR_SIZE);
goto out;
}
#endif
if (bio_is_discard(bio)) goto out;
bio_for_each_segment(bvec, bio, iter){
if(page_get_inode(bio_iter_page(bio, iter)) != dev->sd_cow_inode){
if(!is_initialized){
is_initialized = 1;
start_sect = end_sect;
}
}else{
if(is_initialized && end_sect - start_sect > 0){
ret = inc_make_sset(dev, start_sect, end_sect - start_sect);
if(ret) goto out;
}
is_initialized = 0;
}
end_sect += (bio_iter_len(bio, iter) >> 9);
}
if(is_initialized && end_sect - start_sect > 0){
ret = inc_make_sset(dev, start_sect, end_sect - start_sect);
if(ret) goto out;
}
out:
if(ret){
LOG_ERROR(ret, "error tracing bio for incremental");
tracer_set_fail_state(dev, ret);
ret = 0;
}
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_CALL_ORIG, bio, 0);
#endif
//call the original mrf
ret = elastio_snap_call_mrf(dev->sd_orig_mrf, bio);
return ret;
}
#ifdef USE_BDOPS_SUBMIT_BIO
// Linux version 5.9+
static MRF_RETURN_TYPE tracing_mrf(struct bio *bio){
#else
static MRF_RETURN_TYPE tracing_mrf(struct request_queue *q, struct bio *bio){
#endif
int i, ret = 0;
struct snap_device *dev;
make_request_fn *orig_mrf = NULL;
#if defined HAVE_ENUM_REQ_OPF || defined HAVE_ENUM_REQ_OP
unsigned int bio_op_idx = 0;
#endif
MAYBE_UNUSED(ret);
smp_rmb();
tracer_for_each(dev, i){ // for each snap device
if(!dev || (!test_bit(TRACED, &dev->sd_state) && test_bit(UNVERIFIED, &dev->sd_state))) continue;
#if defined HAVE_ENUM_REQ_OPF || defined HAVE_ENUM_REQ_OP
bio_op_idx = bio_op(bio);
if (likely(bio_op_idx < BIO_STATS_MAX_ELEMENTS)) {
dev->sd_bio_stats_total[bio_op_idx]++;
}
#endif
if (!tracer_matches_bio(dev, bio)) continue;
orig_mrf = dev->sd_orig_mrf;
if(elastio_snap_bio_op_flagged(bio, ELASTIO_SNAP_PASSTHROUGH)){
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_CALL_ORIG, bio, 0);
#endif
elastio_snap_bio_op_clear_flag(bio, ELASTIO_SNAP_PASSTHROUGH);
goto call_orig;
}
if(tracer_should_trace_bio(dev, bio)){
if(test_bit(SNAPSHOT, &dev->sd_state)) {
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_SNAP, bio, 0);
#endif
/*
* Drop in the snapshot mode and
* ignore in the incremental mode
*/
if (bio_is_discard(bio)) {
atomic64_inc(&dev->sd_discard_dropped_cnt);
atomic64_add(bio_size(bio) / 512, &dev->sd_discard_ignored_size);
elastio_snap_bio_endio(bio, -EBUSY);
goto out;
}
ret = snap_trace_bio(dev, bio);
}
else {
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_INC, bio, 0);
#endif
ret = inc_trace_bio(dev, bio);
}
goto out;
}
}
call_orig:
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_CALL_ORIG, bio, 0);
#endif
#ifdef USE_BDOPS_SUBMIT_BIO
// Linux version 5.9+
if (orig_mrf) {
ret = elastio_snap_call_mrf(orig_mrf, bio);
} else if (elastio_snap_bio_bi_disk(bio)->fops->submit_bio) {
if (elastio_snap_bio_bi_disk(bio)->fops->submit_bio == tracing_mrf) {
MRF_SET_RETURN_VALUE(elastio_snap_null_mrf(bio));
} else {
MRF_SET_RETURN_VALUE(elastio_snap_bio_bi_disk(bio)->fops->submit_bio(bio));
}
} else {
LOG_WARN("error finding original_mrf and bio's submit_bio. both are empty");
MRF_SET_RETURN_VALUE(submit_bio_noacct(bio));
}
#else
if(orig_mrf) {
ret = __elastio_snap_call_mrf(orig_mrf, q, bio);
} else {
LOG_WARN("error finding original_mrf");
MRF_SET_RETURN_VALUE(elastio_snap_submit_bio(bio));
}
#endif
out:
MRF_RETURN(ret);
}
#ifndef USE_BDOPS_SUBMIT_BIO
// Linux version < 5.9
static MRF_RETURN_TYPE snap_mrf(struct request_queue *q, struct bio *bio){
struct snap_device *dev = q->queuedata;
#else
// Linux version >= 5.9
static MRF_RETURN_TYPE snap_mrf(struct bio *bio){
struct snap_device *dev = elastio_snap_bio_bi_disk(bio)->queue->queuedata;
#endif
#ifdef NETLINK_DEBUG
nl_trace_event_bio(NL_EVENT_BIO_INCOMING_SNAP_MRF, bio, 0);
#endif
//if a write request somehow gets sent in, discard it
if (bio_data_dir(bio)) {
elastio_snap_bio_endio(bio, -EOPNOTSUPP);
MRF_RETURN(0);
} else if (tracer_read_fail_state(dev)) {
elastio_snap_bio_endio(bio, wrap_err_io(dev));
MRF_RETURN(0);
} else if (!test_bit(ACTIVE, &dev->sd_state)) {
elastio_snap_bio_endio(bio, -EBUSY);
MRF_RETURN(0);
} else if (elastio_snap_request_queue_stopped(dev->sd_queue)) {
LOG_WARN("bio request ignored because the request queue was stopped");
elastio_snap_bio_endio(bio, -EBUSY);
MRF_RETURN(0);
} else if(dev->sd_cow && snap_is_cancelled(dev->sd_cow)){
elastio_snap_bio_endio(bio, -EFBIG);
MRF_RETURN(0);
}
//queue bio for processing by kernel thread
bio_queue_add(&dev->sd_cow_bios, bio);
MRF_RETURN(0);
}
#ifdef HAVE_MERGE_BVEC_FN
#ifdef HAVE_BVEC_MERGE_DATA
static int snap_merge_bvec(struct request_queue *q, struct bvec_merge_data *bvm, struct bio_vec *bvec){
struct snap_device *dev = q->queuedata;
struct request_queue *base_queue = bdev_get_queue(dev->sd_base_dev);
bvm->bi_bdev = dev->sd_base_dev;
return base_queue->merge_bvec_fn(base_queue, bvm, bvec);
}
#else
static int snap_merge_bvec(struct request_queue *q, struct bio *bio_bvm, struct bio_vec *bvec){
struct snap_device *dev = q->queuedata;
struct request_queue *base_queue = bdev_get_queue(dev->sd_base_dev);
bio_bvm->bi_bdev = dev->sd_base_dev;
return base_queue->merge_bvec_fn(base_queue, bio_bvm, bvec);
}
#endif
#endif
/*******************************SETUP HELPER FUNCTIONS********************************/
static int bdev_is_already_traced(const struct block_device *bdev){
int i;
struct snap_device *dev;
tracer_for_each(dev, i){
if(!dev || test_bit(UNVERIFIED, &dev->sd_state)) continue;
if(dev->sd_base_dev == bdev) return 1;
}
return 0;
}
#ifndef USE_BDOPS_SUBMIT_BIO
// Linux version <= 5.8
static int find_orig_mrf(struct block_device *bdev, make_request_fn **mrf){
int i;
struct snap_device *dev;
struct request_queue *q = bdev_get_queue(bdev);
make_request_fn *orig_mrf = elastio_snap_get_bd_mrf(bdev);
if(orig_mrf != tracing_mrf){
#ifdef HAVE_BLK_MQ_MAKE_REQUEST
// Linux version 5.8
if (!orig_mrf){
orig_mrf = elastio_snap_null_mrf;
LOG_DEBUG("original mrf is empty, set to elastio_snap_null_mrf");
}
#endif
*mrf = orig_mrf;
return 0;
}
tracer_for_each(dev, i){
if(!dev || (!test_bit(TRACED, &dev->sd_state) && test_bit(UNVERIFIED, &dev->sd_state))) continue;
if(q == bdev_get_queue(dev->sd_base_dev)){
if (!dev->sd_orig_mrf) {
// match queue and find non null orig mrf bacuase the snap device of current parititon may contain null mrf
continue;
}
*mrf = dev->sd_orig_mrf;
return 0;
}
}
*mrf = NULL;
LOG_ERROR(-EFAULT, "error finding original mrf for bdev");
return -EFAULT;
}
#endif
#ifdef USE_BDOPS_SUBMIT_BIO
// Linux version 5.9+
static int find_orig_fops(struct block_device *bdev, struct block_device_operations **ops, make_request_fn **mrf, struct tracing_ops **tracing_ops){
int i;
struct snap_device *dev;
struct block_device_operations *orig_ops = elastio_snap_get_bd_ops(bdev);
make_request_fn *orig_mrf = orig_ops->submit_bio;
char bdev_name[BDEVNAME_SIZE];
*tracing_ops = NULL;
if(orig_mrf != tracing_mrf){
if (!orig_mrf){
if (!elastio_blk_mq_submit_bio){
LOG_ERROR(-EFAULT, "error finding original mrf, original submit_bio and elastio_snap_null_mrf both are empty");
return -EFAULT;
}
orig_mrf = elastio_snap_null_mrf;
LOG_DEBUG("original mrf is empty, set to elastio_snap_null_mrf = %p; orig ops = %p", orig_mrf, orig_ops);
}
else {
LOG_DEBUG("original mrf is not empty = %p; orig ops = %p", orig_mrf, orig_ops);
}
*ops = orig_ops;
*mrf = orig_mrf;
return 0;
}
else {
LOG_DEBUG("original mrf is already replaced with the tracing_mrf = %p", tracing_mrf);
}
tracer_for_each(dev, i){
if(!dev || test_bit(UNVERIFIED, &dev->sd_state)) continue;
if(orig_ops == elastio_snap_get_bd_ops(dev->sd_base_dev)){
*ops = dev->sd_orig_ops;
*mrf = dev->sd_orig_mrf;
*tracing_ops = tracing_ops_get(dev->sd_tracing_ops);
elastio_snap_bdevname(dev->sd_base_dev, bdev_name);
LOG_DEBUG("found already traced device %s with the same original bd_ops. orig mrf = %p; orig ops = %p; tracing ops = %p", bdev_name, *mrf, *ops, *tracing_ops);
return 0;
}
}
*ops = NULL;
*mrf = NULL;
LOG_ERROR(-EFAULT, "error finding original mrf and bd_ops for bdev");
return -EFAULT;
}
#endif
static int __tracer_should_reset_mrf(const struct snap_device *dev){
int i;
struct snap_device *cur_dev;
#ifndef USE_BDOPS_SUBMIT_BIO
struct request_queue *q = bdev_get_queue(dev->sd_base_dev);
#else
struct block_device_operations *ops = elastio_snap_get_bd_ops(dev->sd_base_dev);
#endif
if(elastio_snap_get_bd_mrf(dev->sd_base_dev) != tracing_mrf) return 0;
if(dev != snap_devices[dev->sd_minor]) return 0;
//return 0 if there is another device tracing the same queue as dev.
if(snap_devices){
tracer_for_each(cur_dev, i){
if(!cur_dev || test_bit(UNVERIFIED, &cur_dev->sd_state) || cur_dev == dev) continue;
#ifndef USE_BDOPS_SUBMIT_BIO
if(q == bdev_get_queue(cur_dev->sd_base_dev)) return 0;
#else
if(ops == elastio_snap_get_bd_ops(cur_dev->sd_base_dev)) return 0;
#endif
}
}
return 1;
}
#ifndef USE_BDOPS_SUBMIT_BIO
static int __tracer_transition_tracing(struct snap_device *dev, struct block_device *bdev, make_request_fn *new_mrf, struct snap_device **dev_ptr, bool start){
#else
static int __tracer_transition_tracing(struct snap_device *dev, struct block_device *bdev, const struct block_device_operations *new_ops, struct snap_device **dev_ptr, bool start){
#endif
int ret;
struct super_block *origsb = elastio_snap_get_super(bdev);
char bdev_name[BDEVNAME_SIZE];
MAYBE_UNUSED(ret);
elastio_snap_bdevname(bdev, bdev_name);
if(origsb){
LOG_DEBUG("force syncing the disk '%s'", bdev_name);
sync_filesystem(origsb);
drop_super(origsb);
//freeze and sync block device
LOG_DEBUG("freezing '%s'", bdev_name);
#ifndef HAVE_FREEZE_SUPER_2
#ifdef HAVE_FREEZE_SUPER_PTR
if (origsb->s_op->freeze_super)
ret = origsb->s_op->freeze_super(origsb);
else
#endif
ret = freeze_super(origsb);
#else
if (origsb->s_op->freeze_super)
ret = origsb->s_op->freeze_super(origsb, FREEZE_HOLDER_KERNEL);
else
ret = freeze_super(origsb, FREEZE_HOLDER_KERNEL);
#endif
/* ret = elastio_snap_freeze_bdev(bdev, &sb); */
if (ret) {
LOG_ERROR((ret), "error freezing '%s': error", bdev_name);
return ret;
}
}
else{
LOG_WARN("warning: no super found for device '%s', unable to freeze it", bdev_name);
}
smp_wmb();
if (start) {
LOG_DEBUG("starting tracing");
if (dev_ptr) *dev_ptr = dev;
smp_wmb();
#ifdef USE_BDOPS_SUBMIT_BIO
if(new_ops) elastio_snap_set_bd_ops(bdev, new_ops);
#ifdef HAVE_BD_HAS_SUBMIT_BIO
bdev->bd_has_submit_bio = true; // kernel version >= 6.4
#elif defined HAVE_BD_HAS_SUBMIT_BIO_FLAGS
bdev_set_flag(bdev, BD_HAS_SUBMIT_BIO);
#endif
#else
if(new_mrf) elastio_snap_set_bd_mrf(bdev, new_mrf);
#endif
} else {
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_TRACING_STARTED, 0);
#endif
LOG_DEBUG("ending tracing");
#ifdef HAVE_BLK_MQ_MAKE_REQUEST
// Linux version 5.8
if(new_mrf) elastio_snap_set_bd_mrf(bdev, new_mrf == elastio_snap_null_mrf ? NULL : new_mrf);
#elif defined USE_BDOPS_SUBMIT_BIO
// Linux version 5.9+
if(new_ops) elastio_snap_set_bd_ops(bdev, new_ops);
#ifdef HAVE_BD_HAS_SUBMIT_BIO
bdev->bd_has_submit_bio = dev->sd_tracing_ops->has_submit_bio; // kernel version >= 6.4
#elif defined HAVE_BD_HAS_SUBMIT_BIO_FLAGS
if (dev->sd_tracing_ops->has_submit_bio)
bdev_set_flag(bdev, BD_HAS_SUBMIT_BIO);
else
bdev_clear_flag(bdev, BD_HAS_SUBMIT_BIO);
#endif
#else
// Linux version older than 5.8
if(new_mrf) elastio_snap_set_bd_mrf(bdev, new_mrf);
#endif
if (dev_ptr) *dev_ptr = NULL;
smp_wmb();
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_TRACING_FINISHED, 0);
#endif
}
if(origsb){
//thaw the block device
LOG_DEBUG("thawing '%s'", bdev_name);
#ifndef HAVE_FREEZE_SUPER_2
#ifdef HAVE_FREEZE_SUPER_PTR
if (origsb->s_op->thaw_super)
ret = origsb->s_op->thaw_super(origsb);
else
#endif
ret = thaw_super(origsb);
#else
if (origsb->s_op->thaw_super)
ret = origsb->s_op->thaw_super(origsb, FREEZE_HOLDER_KERNEL);
else
ret = thaw_super(origsb, FREEZE_HOLDER_KERNEL);
#endif
/* ret = elastio_snap_thaw_bdev(bdev, sb); */
if(ret){
LOG_ERROR(ret, "error thawing '%s'", bdev_name);
//we can't reasonably undo what we've done at this point, and we've replaced the mrf.
//pretend we succeeded so we don't break the block device
}
}
return 0;
}
/*******************************TRACER COMPONENT SETUP / DESTROY FUNCTIONS********************************/
static void __tracer_init(struct snap_device *dev){
LOG_DEBUG("initializing tracer");
atomic_set(&dev->sd_fail_code, 0);
/* Ensure request queue isn't ignored for freshly initialized device */
atomic_set(&dev->sd_ignore_requests, 0);
bio_queue_init(&dev->sd_cow_bios);
bio_queue_init(&dev->sd_orig_bios);
sset_queue_init(&dev->sd_pending_ssets);
}
static int tracer_alloc(struct snap_device **dev_ptr){
int ret;
struct snap_device *dev;
//allocate struct
LOG_DEBUG("allocating device struct");
dev = kzalloc(sizeof(struct snap_device), GFP_KERNEL);
if(!dev){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating memory for device struct");
goto error;
}
__tracer_init(dev);
*dev_ptr = dev;
return 0;
error:
LOG_ERROR(ret, "error allocating device struct");
if(dev) kfree(dev);
*dev_ptr = NULL;
return ret;
}
static void __tracer_destroy_base_dev(struct snap_device *dev){
dev->sd_size = 0;
dev->sd_sect_off = 0;
if(dev->sd_bdev_path){
LOG_DEBUG("freeing base block device path");
kfree(dev->sd_bdev_path);
dev->sd_bdev_path = NULL;
}
if(dev->sd_base_dev){
LOG_DEBUG("freeing base block device");
elastio_snap_blkdev_put(&dev->sd_bdev_container);
dev->sd_base_dev = NULL;
}
}
static int __tracer_setup_base_dev(struct snap_device *dev, const char *bdev_path){
int ret;
//open the base block device
LOG_DEBUG("finding block device");
dev->sd_base_dev = elastio_snap_blkdev_get_by_path(&dev->sd_bdev_container, bdev_path, FMODE_READ, NULL);
if(IS_ERR(dev->sd_base_dev)){
ret = PTR_ERR(dev->sd_base_dev);
dev->sd_base_dev = NULL;
LOG_ERROR(ret, "error finding block device '%s'", bdev_path);
goto error;
}else if(!dev->sd_base_dev->bd_disk){
ret = -EFAULT;
LOG_ERROR(ret, "error finding block device gendisk");
goto error;
}
//check block device is not already being traced
LOG_DEBUG("checking block device is not already being traced");
if(!test_bit(TRACED, &dev->sd_state) && bdev_is_already_traced(dev->sd_base_dev)){
ret = -EINVAL;
LOG_ERROR(ret, "block device is already being traced");
goto error;
}
//fetch the absolute pathname for the base device
LOG_DEBUG("fetching the absolute pathname for the base device");
ret = pathname_to_absolute(bdev_path, &dev->sd_bdev_path, NULL);
if(ret) goto error;
//check if device represents a partition, calculate size and offset
LOG_DEBUG("calculating block device size and offset");
if(elastio_snap_bdev_is_partition(dev->sd_base_dev)){
dev->sd_sect_off = get_start_sect(dev->sd_base_dev);
dev->sd_size = elastio_snap_bdev_size(dev->sd_base_dev);
}else{
dev->sd_sect_off = 0;
dev->sd_size = get_capacity(dev->sd_base_dev->bd_disk);
}
LOG_DEBUG("bdev size = %llu, offset = %llu", (unsigned long long)dev->sd_size, (unsigned long long)dev->sd_sect_off);
return 0;
error:
LOG_ERROR(ret, "error setting up base block device");
__tracer_destroy_base_dev(dev);
return ret;
}
static void __tracer_copy_base_dev(const struct snap_device *src, struct snap_device *dest){
dest->sd_size = src->sd_size;
dest->sd_sect_off = src->sd_sect_off;
dest->sd_base_dev = src->sd_base_dev;
dest->sd_bdev_path = src->sd_bdev_path;
dest->sd_cow_state = src->sd_cow_state;
dest->sd_bdev_container = src->sd_bdev_container;
dest->sd_ignore_snap_errors = src->sd_ignore_snap_errors;
}
static int __tracer_destroy_cow(struct snap_device *dev, int close_method){
int ret = 0;
if(dev->sd_cow){
LOG_DEBUG("destroying cow manager. close method: %d", close_method);
if(close_method == 0){
cow_free(dev->sd_cow);
dev->sd_cow = NULL;
}else if(close_method == 1){
ret = cow_sync_and_free(dev->sd_cow);
dev->sd_cow = NULL;
}else if(close_method == 2){
ret = cow_sync_and_close(dev->sd_cow);
task_work_flush();
}
}
if (close_method != 2 && dev->sd_cow_extents) {
LOG_DEBUG("destroying cow file extents");
kfree(dev->sd_cow_extents);
dev->sd_cow_extents = NULL;
dev->sd_cow_ext_cnt = 0;
dev->sd_cow_inode = NULL;
} else {
LOG_DEBUG("preserving cow file extents");
}
dev->sd_falloc_size = 0;
dev->sd_cache_size = 0;
return ret;
}
#define __tracer_destroy_cow_free(dev) __tracer_destroy_cow(dev, 0)
#define __tracer_destroy_cow_sync_and_free(dev) __tracer_destroy_cow(dev, 1)
#define __tracer_destroy_cow_sync_and_close(dev) __tracer_destroy_cow(dev, 2)
static int file_is_on_bdev(const struct file *file, struct block_device *bdev) {
struct super_block *sb = elastio_snap_get_super(bdev);
int ret = 0;
if (sb) {
ret = ((elastio_snap_get_mnt(file))->mnt_sb == sb);
drop_super(sb);
}
return ret;
}
static int __tracer_setup_cow(struct snap_device *dev, struct block_device *bdev, const char *cow_path,
sector_t size, unsigned long fallocated_space, unsigned long cache_size, const uint8_t *uuid, uint64_t seqid, int open_method)
{
int ret;
uint64_t max_file_size;
char bdev_name[BDEVNAME_SIZE];
char *cow_path_full = (char *)cow_path;
elastio_snap_bdevname(bdev, bdev_name);
if(open_method == 3){
//reopen the cow manager
LOG_DEBUG("reopening the cow manager with file '%s'", cow_path_full);
ret = cow_reopen(dev->sd_cow, cow_path_full);
if(ret) goto error;
}else{
if(!cache_size) dev->sd_cache_size = elastio_snap_cow_max_memory_default;
else dev->sd_cache_size = cache_size;
if(open_method == 0){
//calculate how much space should be allocated to the cow file
if(!fallocated_space){
max_file_size = size * SECTOR_SIZE * elastio_snap_cow_fallocate_percentage_default;
do_div(max_file_size, 100);
max_file_size = ALIGN(max_file_size, PAGE_SIZE);
}else{
max_file_size = ALIGN(fallocated_space * (1024 * 1024), PAGE_SIZE);
}
dev->sd_falloc_size = max_file_size;
do_div(dev->sd_falloc_size, (1024 * 1024));
//create and open the cow manager
LOG_DEBUG("creating cow manager");
ret = cow_init(dev, cow_path_full, SECTOR_TO_BLOCK(size), COW_SECTION_SIZE, dev->sd_cache_size, max_file_size, uuid, seqid, &dev->sd_cow);
if(ret) goto error;
}else{
//reload the cow manager
LOG_DEBUG("reloading cow manager");
ret = cow_reload(dev, cow_path_full, SECTOR_TO_BLOCK(size), COW_SECTION_SIZE, dev->sd_cache_size, (open_method == 2), &dev->sd_cow);
if(ret) goto error;
dev->sd_falloc_size = dev->sd_cow->file_max;
do_div(dev->sd_falloc_size, (1024 * 1024));
}
}
//set state flag that file is on block device
if (file_is_on_bdev(dev->sd_cow->filp, bdev)) {
set_bit(COW_ON_BDEV, &dev->sd_cow_state);
//find the cow file's inode number
LOG_DEBUG("finding cow file inode");
dev->sd_cow_inode = elastio_snap_get_dentry(dev->sd_cow->filp)->d_inode;
}
if(cow_path_full != cow_path) kfree(cow_path_full);
return 0;
error:
LOG_ERROR(ret, "error setting up cow manager");
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_ERROR, ret);
#endif
if(open_method != 3) __tracer_destroy_cow_free(dev);
if(cow_path_full != cow_path) kfree(cow_path_full);
return ret;
}
#define __tracer_setup_cow_new(dev, bdev, cow_path, size, fallocated_space, cache_size, uuid, seqid) __tracer_setup_cow(dev, bdev, cow_path, size, fallocated_space, cache_size, uuid, seqid, 0)
#define __tracer_setup_cow_reload_snap(dev, bdev, cow_path, size, cache_size) __tracer_setup_cow(dev, bdev, cow_path, size, 0, cache_size, NULL, 0, 1)
#define __tracer_setup_cow_reload_inc(dev, bdev, cow_path, size, cache_size) __tracer_setup_cow(dev, bdev, cow_path, size, 0, cache_size, NULL, 0, 2)
#define __tracer_setup_cow_reopen(dev, bdev, cow_path) __tracer_setup_cow(dev, bdev, cow_path, 0, 0, 0, NULL, 0, 3)
static void __tracer_copy_cow(const struct snap_device *src, struct snap_device *dest){
dest->sd_cow = src->sd_cow;
// copy cow file extents and update the device
dest->sd_cow_extents = src->sd_cow_extents;
dest->sd_cow_ext_cnt = src->sd_cow_ext_cnt;
dest->sd_cow->dev = dest;
dest->sd_cow_inode = src->sd_cow_inode;
dest->sd_cache_size = src->sd_cache_size;
dest->sd_falloc_size = src->sd_falloc_size;
}
static void __tracer_destroy_cow_path(struct snap_device *dev){
if(dev->sd_cow_path){
LOG_DEBUG("freeing cow path");
kfree(dev->sd_cow_path);
dev->sd_cow_path = NULL;
}
}
static int __tracer_setup_cow_path(struct snap_device *dev, const struct file *cow_file){
int ret;
//get the pathname of the cow file (relative to the mountpoint)
if(test_bit(COW_ON_BDEV, &dev->sd_cow_state)){
LOG_DEBUG("getting relative pathname of cow file");
ret = dentry_get_relative_pathname(elastio_snap_get_dentry(cow_file), &dev->sd_cow_path, NULL);
}else{
LOG_DEBUG("getting absolute pathname of cow file");
ret = file_get_absolute_pathname(cow_file, &dev->sd_cow_path, NULL);
}
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error setting up cow file path");
__tracer_destroy_cow_path(dev);
return ret;
}
static void __tracer_copy_cow_path(const struct snap_device *src, struct snap_device *dest){
dest->sd_cow_path = src->sd_cow_path;
}
static inline void __tracer_bioset_exit(struct snap_device *dev){
#ifndef HAVE_BIOSET_INIT
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,18,0)
if(dev->sd_bioset){
LOG_DEBUG("freeing bio set");
bioset_free(dev->sd_bioset);
dev->sd_bioset = NULL;
}
#else
bioset_exit(&dev->sd_bioset);
#endif
}
static void __tracer_destroy_snap(struct snap_device *dev){
if(dev->sd_mrf_thread){
LOG_DEBUG("stopping mrf thread");
kthread_stop(dev->sd_mrf_thread);
dev->sd_mrf_thread = NULL;
}
if(dev->sd_gd){
LOG_DEBUG("freeing gendisk");
#ifdef HAVE_DISK_LIVE
// Kernel version 5.15+
if(disk_live(dev->sd_gd)) del_gendisk(dev->sd_gd);
#else
if(dev->sd_gd->flags & GENHD_FL_UP) del_gendisk(dev->sd_gd);
#endif
if(dev->sd_queue){
LOG_DEBUG("freeing request queue");
#if defined HAVE_ALLOC_DISK
blk_cleanup_queue(dev->sd_queue);
#endif
}
put_disk(dev->sd_gd);
dev->sd_queue = NULL;
dev->sd_gd = NULL;
}
if(dev->sd_queue){
LOG_DEBUG("freeing request queue");
#if defined HAVE_ALLOC_DISK
blk_cleanup_queue(dev->sd_queue);
#else
blk_put_queue(dev->sd_queue);
#endif
dev->sd_queue = NULL;
}
__tracer_bioset_exit(dev);
}
static int __tracer_bioset_init(struct snap_device *dev){
#ifndef HAVE_BIOSET_INIT
//#if LINUX_VERSION_CODE < KERNEL_VERSION(4,18,0)
dev->sd_bioset = elastio_snap_bioset_create(BIO_SET_SIZE, BIO_SET_SIZE, 0);
if(!dev->sd_bioset) return -ENOMEM;
return 0;
#else
return bioset_init(&dev->sd_bioset, BIO_SET_SIZE, BIO_SET_SIZE, BIOSET_NEED_BVECS);
#endif
}
static int __tracer_setup_snap(struct snap_device *dev, unsigned int minor, struct block_device *bdev, sector_t size){
int ret;
ret = __tracer_bioset_init(dev);
if(ret){
LOG_ERROR(ret, "error initializing bio set");
goto error;
}
// alloc_disk() function has disappeared starting from the kernel 5.15
#ifdef HAVE_ALLOC_DISK
LOG_DEBUG("allocating queue and setting up make request function");
#ifdef HAVE_BLK_ALLOC_QUEUE_MK_REQ_FN_NODE_ID
dev->sd_queue = blk_alloc_queue(snap_mrf, NUMA_NO_NODE);
#else
dev->sd_queue = elastio_blk_alloc_queue(GFP_KERNEL);
#endif
if(!dev->sd_queue){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating request queue");
goto error;
}
LOG_DEBUG("allocating gendisk");
dev->sd_gd = alloc_disk(1);
if(!dev->sd_gd){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating gendisk");
goto error;
}
dev->sd_gd->queue = dev->sd_queue;
#else
LOG_DEBUG("allocating gendisk & queue");
dev->sd_gd = elastio_snap_blk_alloc_disk();
if(!dev->sd_gd){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating gendisk");
goto error;
}
dev->sd_queue = dev->sd_gd->queue;
#endif
#ifndef HAVE_BLK_ALLOC_QUEUE_MK_REQ_FN_NODE_ID
LOG_DEBUG("setting up make request function");
// For the Linux kernel version 5.9+:
// The snap_mrf function is already set in the block_device_operations snap_ops struct
// as submit_bio func. So, the request handler is already registered.
// See a line below "dev->sd_gd->fops = &snap_ops;"
#ifndef USE_BDOPS_SUBMIT_BIO
// Linux kernel version <= 5.6
// register request handler
blk_queue_make_request(dev->sd_queue, snap_mrf);
#endif
#endif
//give our request queue the same properties as the base device's
LOG_DEBUG("setting queue limits");
blk_set_stacking_limits(&dev->sd_queue->limits);
elastio_snap_bdev_stack_limits(dev->sd_queue, bdev, 0);
#ifdef HAVE_MERGE_BVEC_FN
//use a thin wrapper around the base device's merge_bvec_fn
if(bdev_get_queue(bdev)->merge_bvec_fn) blk_queue_merge_bvec(dev->sd_queue, snap_merge_bvec);
#endif
//initialize gendisk and request queue values
LOG_DEBUG("initializing gendisk");
dev->sd_queue->queuedata = dev;
dev->sd_gd->private_data = dev;
dev->sd_gd->major = major;
#ifndef HAVE_ALLOC_DISK
dev->sd_gd->minors = 1;
#endif
dev->sd_gd->first_minor = minor;
dev->sd_gd->fops = &snap_ops;
//name our gendisk
LOG_DEBUG("naming gendisk");
snprintf(dev->sd_gd->disk_name, 32, SNAP_DEVICE_NAME, minor);
//set the capacity of our gendisk
LOG_DEBUG("block device size: %llu", (unsigned long long)size);
set_capacity(dev->sd_gd, size);
#ifdef HAVE_GENHD_FL_NO_PART_SCAN
//#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
//disable partition scanning (the device should not have any sub-partitions)
dev->sd_gd->flags |= GENHD_FL_NO_PART_SCAN;
#endif
#ifdef HAVE_GENHD_FL_NO_PART
// the flag has been renamed in 5.17
dev->sd_gd->flags |= GENHD_FL_NO_PART;
#endif
//set the device as read-only
set_disk_ro(dev->sd_gd, 1);
//register gendisk with the kernel
LOG_DEBUG("adding disk");
#ifdef HAVE_ADD_DISK_INT
ret = add_disk(dev->sd_gd);
if(ret){
LOG_ERROR(ret, "error adding disk");
goto error;
}
#else
add_disk(dev->sd_gd);
#endif
LOG_DEBUG("starting mrf kernel thread");
dev->sd_mrf_thread = kthread_run(snap_mrf_thread, dev, SNAP_MRF_THREAD_NAME_FMT, minor);
if(IS_ERR(dev->sd_mrf_thread)){
ret = PTR_ERR(dev->sd_mrf_thread);
dev->sd_mrf_thread = NULL;
LOG_ERROR(ret, "error starting mrf kernel thread");
goto error;
}
atomic64_set(&dev->sd_submitted_cnt, 0);
atomic64_set(&dev->sd_received_cnt, 0);
atomic64_set(&dev->sd_processed_cnt, 0);
atomic64_set(&dev->sd_discard_dropped_cnt, 0);
atomic64_set(&dev->sd_discard_ignored_size, 0);
return 0;
error:
LOG_ERROR(ret, "error setting up snapshot");
__tracer_destroy_snap(dev);
return ret;
}
static void __tracer_destroy_cow_thread(struct snap_device *dev){
if(dev->sd_cow_thread){
LOG_DEBUG("stopping cow thread");
/* Need to wait the readers to complete, otherwise
* unprocessed bio requests will be frozen in the
* driver's bio queue and hence freeze the driver
* Please refer to:
* - https://github.com/elastio/elastio-snap/issues/169
* - https://jira.slc.efscloud.net/browse/RA-5394
* for the issue description
*/
elastio_snap_stop_request_queue(dev->sd_queue);
elastio_snap_wait_for_release(dev);
kthread_stop(dev->sd_cow_thread);
dev->sd_cow_thread = NULL;
}
}
static int __tracer_setup_cow_thread(struct snap_device *dev, unsigned int minor, int is_snap){
int ret;
LOG_DEBUG("creating kernel cow thread");
if(is_snap) dev->sd_cow_thread = kthread_create(snap_cow_thread, dev, SNAP_COW_THREAD_NAME_FMT, minor);
else dev->sd_cow_thread = kthread_create(inc_sset_thread, dev, INC_THREAD_NAME_FMT, minor);
if(IS_ERR(dev->sd_cow_thread)){
ret = PTR_ERR(dev->sd_cow_thread);
dev->sd_cow_thread = NULL;
LOG_ERROR(ret, "error creating kernel thread");
goto error;
}
/* Ensure request queue is enabled after (re)setup in case it was stopped earlier */
atomic_set(&dev->sd_ignore_requests, 0);
return 0;
error:
LOG_ERROR(ret, "error setting up cow thread");
__tracer_destroy_cow_thread(dev);
return ret;
}
#define __tracer_setup_inc_cow_thread(dev, minor) __tracer_setup_cow_thread(dev, minor, 0)
#define __tracer_setup_snap_cow_thread(dev, minor) __tracer_setup_cow_thread(dev, minor, 1)
static void minor_range_recalculate(void){
unsigned int i, highest = 0, lowest = elastio_snap_max_snap_devices - 1;
struct snap_device *dev;
tracer_for_each_full(dev, i){
if(!dev) continue;
if(i < lowest) lowest = i;
if(i > highest) highest = i;
}
lowest_minor = lowest;
highest_minor = highest;
}
static void minor_range_include(unsigned int minor){
if(minor < lowest_minor) lowest_minor = minor;
if(minor > highest_minor) highest_minor = minor;
}
static inline void free_mrf_and_ops(struct snap_device *dev){
#ifdef USE_BDOPS_SUBMIT_BIO
if (dev->sd_tracing_ops) {
tracing_ops_put(dev->sd_tracing_ops);
dev->sd_tracing_ops = NULL;
}
dev->sd_orig_ops = NULL;
#endif
dev->sd_orig_mrf = NULL;
}
static void __tracer_destroy_tracing(struct snap_device *dev){
if(dev->sd_orig_mrf){
if(__tracer_should_reset_mrf(dev)) {
if (!test_bit(ACTIVE, &dev->sd_state)) {
int ret = 0;
LOG_DEBUG("flushing bio requests");
if (!test_bit(SNAPSHOT, &dev->sd_state)) {
ret = __tracer_setup_inc_cow_thread(dev, dev->sd_minor);
} else {
ret = __tracer_setup_snap_cow_thread(dev, dev->sd_minor);
}
if(ret) {
LOG_ERROR(ret, "Failed to setup cow thread for device with minor %i and flush bio requests", dev->sd_minor);
}
wake_up_process(dev->sd_cow_thread);
wait_for_bio_complete(dev);
__tracer_destroy_cow_thread(dev);
}
LOG_DEBUG("replacing make_request_fn");
#ifdef USE_BDOPS_SUBMIT_BIO
__tracer_transition_tracing(dev, dev->sd_base_dev, dev->sd_orig_ops, &snap_devices[dev->sd_minor], false);
#else
__tracer_transition_tracing(dev, dev->sd_base_dev, dev->sd_orig_mrf, &snap_devices[dev->sd_minor], false);
#endif
}
else {
LOG_DEBUG("no need to replace make_request_fn");
__tracer_transition_tracing(dev, dev->sd_base_dev, NULL, &snap_devices[dev->sd_minor], false);
}
smp_wmb();
free_mrf_and_ops(dev);
}else if(snap_devices[dev->sd_minor] == dev){
smp_wmb();
snap_devices[dev->sd_minor] = NULL;
smp_wmb();
}
dev->sd_minor = 0;
minor_range_recalculate();
}
static void __tracer_setup_tracing_unverified(struct snap_device *dev, unsigned int minor){
free_mrf_and_ops(dev);
minor_range_include(minor);
smp_wmb();
dev->sd_minor = minor;
snap_devices[minor] = dev;
smp_wmb();
}
static int __tracer_setup_tracing(struct snap_device *dev, unsigned int minor){
int ret = 0;
dev->sd_minor = minor;
minor_range_include(minor);
//get the base block device's make_request_fn
LOG_DEBUG("getting the base block device's make_request_fn");
#ifndef USE_BDOPS_SUBMIT_BIO
ret = find_orig_mrf(dev->sd_base_dev, &dev->sd_orig_mrf);
if(ret) goto error;
ret = __tracer_transition_tracing(dev, dev->sd_base_dev, tracing_mrf, &snap_devices[minor], true);
#else
if (!dev->sd_tracing_ops) {
// Multiple devices on the same disk are sharing block_device_operations struct.
// The next call will set a pointer to the dev->sd_tracing_ops if some device at the same disk is
// already tracked by the driver. And we'll reuse the existing struct in this case.
ret = find_orig_fops(dev->sd_base_dev, &dev->sd_orig_ops, &dev->sd_orig_mrf, &dev->sd_tracing_ops);
if(ret) goto error;
if (!dev->sd_tracing_ops) {
LOG_DEBUG("allocating tracing ops for device with minor %i", minor);
ret = tracing_ops_alloc(dev);
if (ret) goto error;
}
else {
LOG_DEBUG("using already existing tracing ops for device with minor %i", minor);
}
ret = __tracer_transition_tracing(dev, dev->sd_base_dev, dev->sd_tracing_ops->bd_ops, &snap_devices[minor], true);
}
else {
LOG_DEBUG("device with minor %i already has sd_tracing_ops", minor);
}
#endif
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error setting up tracing");
dev->sd_minor = 0;
free_mrf_and_ops(dev);
minor_range_recalculate();
return ret;
}
/************************SETUP / DESTROY FUNCTIONS************************/
static void tracer_destroy(struct snap_device *dev){
__tracer_destroy_tracing(dev);
__tracer_destroy_cow_thread(dev);
__tracer_destroy_snap(dev);
__tracer_destroy_cow_path(dev);
__tracer_destroy_cow_free(dev);
__tracer_destroy_base_dev(dev);
}
static int tracer_setup_active_snap(struct snap_device *dev, unsigned int minor, const char *bdev_path, const char *cow_path, unsigned long fallocated_space, unsigned long cache_size, bool ignore_snap_errors){
int ret;
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_SETUP_SNAPSHOT, 0);
#endif
set_bit(SNAPSHOT, &dev->sd_state);
set_bit(ACTIVE, &dev->sd_state);
clear_bit(UNVERIFIED, &dev->sd_state);
dev->sd_ignore_snap_errors = ignore_snap_errors;
//setup base device
ret = __tracer_setup_base_dev(dev, bdev_path);
if(ret) goto error;
//setup the cow manager
ret = __tracer_setup_cow_new(dev, dev->sd_base_dev, cow_path, dev->sd_size, fallocated_space, cache_size, NULL, 1);
if(ret) goto error;
//setup the cow path
ret = __tracer_setup_cow_path(dev, dev->sd_cow->filp);
if(ret) goto error;
//setup the snapshot values
ret = __tracer_setup_snap(dev, minor, dev->sd_base_dev, dev->sd_size);
if(ret) goto error;
//setup the cow thread and run it
ret = __tracer_setup_snap_cow_thread(dev, minor);
if(ret) goto error;
wake_up_process(dev->sd_cow_thread);
//inject the tracing function
ret = __tracer_setup_tracing(dev, minor);
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error setting up tracer as active snapshot");
tracer_destroy(dev);
return ret;
}
static int __tracer_setup_unverified(struct snap_device *dev, unsigned int minor, const char *bdev_path, const char *cow_path, unsigned long cache_size, bool ignore_snap_errors, int is_snap){
if (is_snap) {
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_SETUP_UNVERIFIED_SNAP, 0);
#endif
set_bit(SNAPSHOT, &dev->sd_state);
} else {
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_SETUP_UNVERIFIED_INC, 0);
#endif
clear_bit(SNAPSHOT, &dev->sd_state);
}
clear_bit(ACTIVE, &dev->sd_state);
set_bit(UNVERIFIED, &dev->sd_state);
dev->sd_cache_size = cache_size;
dev->sd_ignore_snap_errors = ignore_snap_errors;
dev->sd_bdev_path = kstrdup(bdev_path, GFP_KERNEL);
if(!dev->sd_bdev_path) goto error;
dev->sd_cow_path = kstrdup(cow_path, GFP_KERNEL);
if(!dev->sd_cow_path) goto error;
//add the tracer to the array of devices
__tracer_setup_tracing_unverified(dev, minor);
return 0;
error:
LOG_ERROR(-ENOMEM, "error setting up unverified tracer");
tracer_destroy(dev);
return -ENOMEM;
}
#define tracer_setup_unverified_inc(dev, minor, bdev_path, cow_path, cache_size, ignore_snap_errors) __tracer_setup_unverified(dev, minor, bdev_path, cow_path, cache_size, ignore_snap_errors, 0)
#define tracer_setup_unverified_snap(dev, minor, bdev_path, cow_path, cache_size, ignore_snap_errors) __tracer_setup_unverified(dev, minor, bdev_path, cow_path, cache_size, ignore_snap_errors, 1)
/************************IOCTL TRANSITION FUNCTIONS************************/
static int tracer_active_snap_to_inc(struct snap_device *old_dev){
int ret;
struct snap_device *dev;
char *abs_path = NULL;
int abs_path_len;
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_TRANSITION_INC, 0);
#endif
//allocate new tracer
ret = tracer_alloc(&dev);
if(ret) return ret;
clear_bit(SNAPSHOT, &dev->sd_state);
set_bit(ACTIVE, &dev->sd_state);
clear_bit(UNVERIFIED, &dev->sd_state);
//copy / set fields we need
__tracer_copy_base_dev(old_dev, dev);
__tracer_copy_cow_path(old_dev, dev);
//copy cow manager to new device. Care must be taken to make sure it isn't used by multiple threads at once.
__tracer_copy_cow(old_dev, dev);
//setup the cow thread
ret = __tracer_setup_inc_cow_thread(dev, old_dev->sd_minor);
if(ret) goto error;
//inject the tracing function
ret = __tracer_setup_tracing(dev, old_dev->sd_minor);
if(ret) goto error;
//Below this point, we are commited to the new device, so we must make sure it is in a good state.
//stop the old cow thread. Must be done before starting the new cow thread to prevent concurrent access.
__tracer_destroy_cow_thread(old_dev);
//sanity check to ensure no errors have occurred while cleaning up the old cow thread
ret = tracer_read_fail_state(old_dev);
if(ret){
LOG_ERROR(ret, "errors occurred while cleaning up cow thread, putting incremental into error state");
tracer_set_fail_state(dev, ret);
//must make up the new thread regardless of errors so that any queued ssets are cleaned up
wake_up_process(dev->sd_cow_thread);
//clean up the old device no matter what
__tracer_destroy_snap(old_dev);
kfree(old_dev);
return ret;
}
//wake up new cow thread. Must happen regardless of errors syncing the old cow thread in order to ensure no IO's are leaked.
wake_up_process(dev->sd_cow_thread);
//truncate the cow file
ret = cow_truncate_to_index(dev->sd_cow);
if(ret){
//not a critical error, we can just print a warning
file_get_absolute_pathname(dev->sd_cow->filp, &abs_path, &abs_path_len);
if(!abs_path){
LOG_WARN("warning: cow file truncation failed, incremental will use more disk space than needed");
}else{
LOG_WARN("warning: failed to truncate '%s', incremental will use more disk space than needed", abs_path);
kfree(abs_path);
}
}
//destroy the unneeded fields of the old_dev and the old_dev itself
__tracer_destroy_snap(old_dev);
kfree(old_dev);
return 0;
error:
LOG_ERROR(ret, "error transitioning to incremental mode");
__tracer_destroy_cow_thread(dev);
kfree(dev);
return ret;
}
static int tracer_active_inc_to_snap(struct snap_device *old_dev, const char *cow_path, unsigned long fallocated_space){
int ret;
struct snap_device *dev;
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_TRANSITION_SNAP, 0);
#endif
//allocate new tracer
ret = tracer_alloc(&dev);
if(ret) return ret;
set_bit(SNAPSHOT, &dev->sd_state);
set_bit(ACTIVE, &dev->sd_state);
clear_bit(UNVERIFIED, &dev->sd_state);
//copy / set fields we need
__tracer_copy_base_dev(old_dev, dev);
//setup the cow manager
ret = __tracer_setup_cow_new(dev, dev->sd_base_dev, cow_path, dev->sd_size, fallocated_space, dev->sd_cache_size, old_dev->sd_cow->uuid, old_dev->sd_cow->seqid + 1);
if(ret) goto error;
//setup the cow path
ret = __tracer_setup_cow_path(dev, dev->sd_cow->filp);
if(ret) goto error;
//setup the snapshot values
ret = __tracer_setup_snap(dev, old_dev->sd_minor, dev->sd_base_dev, dev->sd_size);
if(ret) goto error;
//setup the cow thread
ret = __tracer_setup_snap_cow_thread(dev, old_dev->sd_minor);
if(ret) goto error;
//start tracing (overwrites old_dev's tracing)
ret = __tracer_setup_tracing(dev, old_dev->sd_minor);
if(ret) goto error;
//stop the old cow thread and start the new one
__tracer_destroy_cow_thread(old_dev);
wake_up_process(dev->sd_cow_thread);
//destroy the unneeded fields of the old_dev and the old_dev itself
__tracer_destroy_cow_path(old_dev);
__tracer_destroy_cow_sync_and_free(old_dev);
kfree(old_dev);
return 0;
error:
LOG_ERROR(ret, "error transitioning tracer to snapshot mode");
__tracer_destroy_cow_thread(dev);
__tracer_destroy_snap(dev);
__tracer_destroy_cow_path(dev);
__tracer_destroy_cow_free(dev);
kfree(dev);
return ret;
}
static void tracer_reconfigure(struct snap_device *dev, unsigned long cache_size){
dev->sd_cache_size = cache_size;
if(!cache_size) cache_size = elastio_snap_cow_max_memory_default;
if(test_bit(ACTIVE, &dev->sd_state)) cow_modify_cache_size(dev->sd_cow, cache_size);
}
static void tracer_elastio_snap_info(const struct snap_device *dev, struct elastio_snap_info *info){
info->is_cancelled = dev->sd_cow ? snap_is_cancelled(dev->sd_cow) : false;
info->minor = dev->sd_minor;
info->state = dev->sd_state;
info->error = tracer_read_fail_state(dev);
info->cache_size = (dev->sd_cache_size)? dev->sd_cache_size : elastio_snap_cow_max_memory_default;
info->ignore_snap_errors = dev->sd_ignore_snap_errors;
elastio_strscpy(info->cow, dev->sd_cow_path, PATH_MAX);
elastio_strscpy(info->bdev, dev->sd_bdev_path, PATH_MAX);
if(!test_bit(UNVERIFIED, &dev->sd_state)){
info->falloc_size = dev->sd_cow->file_max;
info->seqid = dev->sd_cow->seqid;
memcpy(info->uuid, dev->sd_cow->uuid, COW_UUID_SIZE);
info->version = dev->sd_cow->version;
info->nr_changed_blocks = dev->sd_cow->nr_changed_blocks;
}else{
info->falloc_size = 0;
info->seqid = 0;
memset(info->uuid, 0, COW_UUID_SIZE);
}
info->flags = (unsigned int)dev->sd_cow_state; //TODO: May be rename to sd_flags
}
/************************IOCTL HANDLER FUNCTIONS************************/
static int __verify_minor(unsigned int minor, int mode){
//check minor number is within range
if(minor >= elastio_snap_max_snap_devices){
LOG_ERROR(-EINVAL, "minor number specified is out of range");
return -EINVAL;
}
//check if the device is in use
if(mode == 0){
if(snap_devices[minor]){
LOG_ERROR(-EBUSY, "device specified already exists");
return -EBUSY;
}
}else{
if(!snap_devices[minor]){
LOG_ERROR(-ENOENT, "device specified does not exist");
return -ENOENT;
}
//check that the device is not busy if we care
if(mode == 1 && atomic_read(&snap_devices[minor]->sd_refs)){
LOG_ERROR(-EBUSY, "device specified is busy");
return -EBUSY;
}
}
return 0;
}
#define verify_minor_available(minor) __verify_minor(minor, 0)
#define verify_minor_in_use_not_busy(minor) __verify_minor(minor, 1)
#define verify_minor_in_use(minor) __verify_minor(minor, 2)
static int __verify_bdev_writable(const char *bdev_path, int *out){
int writable = 0;
struct block_device *bdev;
struct super_block *sb;
struct bdev_container bd_c;
//open the base block device
bdev = elastio_snap_blkdev_get_by_path(&bd_c, bdev_path, FMODE_READ, NULL);
if(IS_ERR(bdev)){
*out = 0;
return PTR_ERR(bdev);
}
sb = elastio_snap_get_super(bdev);
if(sb){
writable = !(sb->s_flags & MS_RDONLY);
drop_super(sb);
}
elastio_snap_blkdev_put(&bd_c);
*out = writable;
return 0;
}
static int __ioctl_setup(unsigned int minor, const char *bdev_path, const char *cow_path, unsigned long fallocated_space, unsigned long cache_size, bool ignore_snap_errors, int is_snap, int is_reload){
int ret, is_mounted;
struct snap_device *dev = NULL;
LOG_DEBUG("received %s %s ioctl - %u : %s : %s : %s", (is_reload)? "reload" : "setup", (is_snap)? "snap" : "inc", minor, bdev_path, cow_path, (ignore_snap_errors)? "ignore_errors" : "no_ignore_errors");
//verify that the minor number is valid
ret = verify_minor_available(minor);
if(ret) goto error;
//check if block device is mounted
ret = __verify_bdev_writable(bdev_path, &is_mounted);
if(ret) goto error;
//check that reload / setup command matches current mount state
if(is_mounted && is_reload){
ret = -EINVAL;
LOG_ERROR(ret, "illegal to perform reload while mounted");
goto error;
}else if(!is_mounted && !is_reload){
ret = -EINVAL;
LOG_ERROR(ret, "illegal to perform setup while unmounted");
goto error;
}
//allocate the tracing struct
ret = tracer_alloc(&dev);
if(ret) goto error;
//route to the appropriate setup function
if(is_snap){
if(is_mounted) ret = tracer_setup_active_snap(dev, minor, bdev_path, cow_path, fallocated_space, cache_size, ignore_snap_errors);
else ret = tracer_setup_unverified_snap(dev, minor, bdev_path, cow_path, cache_size, ignore_snap_errors);
}else{
if(!is_mounted) ret = tracer_setup_unverified_inc(dev, minor, bdev_path, cow_path, cache_size, ignore_snap_errors);
else{
ret = -EINVAL;
LOG_ERROR(ret, "illegal to setup as active incremental");
goto error;
}
}
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error during setup ioctl handler");
if(dev) kfree(dev);
return ret;
}
#define ioctl_setup_snap(minor, bdev_path, cow_path, fallocated_space, cache_size, ignore_snap_errors) __ioctl_setup(minor, bdev_path, cow_path, fallocated_space, cache_size, ignore_snap_errors, 1, 0)
#define ioctl_reload_snap(minor, bdev_path, cow_path, cache_size, ignore_snap_errors) __ioctl_setup(minor, bdev_path, cow_path, 0, cache_size, ignore_snap_errors, 1, 1)
#define ioctl_reload_inc(minor, bdev_path, cow_path, cache_size, ignore_snap_errors) __ioctl_setup(minor, bdev_path, cow_path, 0, cache_size, ignore_snap_errors, 0, 1)
static int ioctl_destroy(unsigned int minor){
int ret;
struct snap_device *dev;
LOG_DEBUG("received destroy ioctl - %u", minor);
//verify that the minor number is valid
ret = verify_minor_in_use_not_busy(minor);
if(ret){
LOG_ERROR(ret, "error during destroy ioctl handler");
return ret;
}
dev = snap_devices[minor];
wait_for_bio_complete(dev);
tracer_destroy(snap_devices[minor]);
kfree(dev);
return 0;
}
static int ioctl_transition_inc(unsigned int minor){
int ret;
struct snap_device *dev;
LOG_DEBUG("received transition inc ioctl - %u", minor);
//verify that the minor number is valid
ret = verify_minor_in_use_not_busy(minor);
if(ret) goto error;
dev = snap_devices[minor];
wait_for_bio_complete(dev);
//check that the device is not in the fail state
ret = tracer_read_fail_state(dev);
if (ret) {
LOG_ERROR(ret, "device specified is in the fail state");
goto error;
}
//check that tracer is in active snapshot state
if(!test_bit(SNAPSHOT, &dev->sd_state) || !test_bit(ACTIVE, &dev->sd_state)){
ret = -EINVAL;
LOG_ERROR(ret, "device specified is not in active snapshot mode");
goto error;
}
ret = tracer_active_snap_to_inc(dev);
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error during transition to incremental ioctl handler");
return ret;
}
static int ioctl_transition_snap(unsigned int minor, const char *cow_path, unsigned long fallocated_space){
int ret;
struct snap_device *dev;
LOG_DEBUG("received transition snap ioctl - %u : %s", minor, cow_path);
//verify that the minor number is valid
ret = verify_minor_in_use_not_busy(minor);
if(ret) goto error;
dev = snap_devices[minor];
wait_for_bio_complete(dev);
//check that the device is not in the fail state
if(tracer_read_fail_state(dev)){
ret = -EINVAL;
LOG_ERROR(ret, "device specified is in the fail state");
goto error;
}
//check that tracer is in active incremental state
if(test_bit(SNAPSHOT, &dev->sd_state) || !test_bit(ACTIVE, &dev->sd_state)){
ret = -EINVAL;
LOG_ERROR(ret, "device specified is not in active incremental mode");
goto error;
}
ret = tracer_active_inc_to_snap(dev, cow_path, fallocated_space);
if(ret) goto error;
return 0;
error:
LOG_ERROR(ret, "error during transition to snapshot ioctl handler");
return ret;
}
static int ioctl_reconfigure(unsigned int minor, unsigned long cache_size){
int ret;
struct snap_device *dev;
LOG_DEBUG("received reconfigure ioctl - %u : %lu", minor, cache_size);
//verify that the minor number is valid
ret = verify_minor_in_use_not_busy(minor);
if(ret) goto error;
dev = snap_devices[minor];
wait_for_bio_complete(dev);
//check that the device is not in the fail state
if(tracer_read_fail_state(dev)){
ret = -EINVAL;
LOG_ERROR(ret, "device specified is in the fail state");
goto error;
}
tracer_reconfigure(dev, cache_size);
return 0;
error:
LOG_ERROR(ret, "error during reconfigure ioctl handler");
return ret;
}
static int ioctl_elastio_snap_info(struct elastio_snap_info *info){
int ret;
struct snap_device *dev;
LOG_DEBUG("received elastio-snap info ioctl - %u", info->minor);
//verify that the minor number is valid
ret = verify_minor_in_use(info->minor);
if(ret) goto error;
dev = snap_devices[info->minor];
tracer_elastio_snap_info(dev, info);
return 0;
error:
LOG_ERROR(ret, "error during reconfigure ioctl handler");
return ret;
}
static int get_free_minor(void)
{
struct snap_device *dev;
int i;
tracer_for_each_full(dev, i){
if(!dev) return i;
}
return -ENOENT;
}
static long ctrl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){
int ret, idx;
char *bdev_path = NULL;
char *cow_path = NULL;
struct elastio_snap_info *info = NULL;
unsigned int minor = 0;
unsigned long fallocated_space = 0, cache_size = 0;
bool ignore_snap_errors = false;
LOG_DEBUG("ioctl command received: %d", cmd);
mutex_lock(&ioctl_mutex);
switch(cmd){
case IOCTL_SETUP_SNAP:
//get params from user space
ret = get_setup_params((struct setup_params __user *)arg, &minor, &bdev_path, &cow_path, &fallocated_space, &cache_size, &ignore_snap_errors);
if(ret) break;
ret = ioctl_setup_snap(minor, bdev_path, cow_path, fallocated_space, cache_size, ignore_snap_errors);
if(ret) break;
elastio_snap_wait_for_release(snap_devices[minor]);
break;
case IOCTL_RELOAD_SNAP:
//get params from user space
ret = get_reload_params((struct reload_params __user *)arg, &minor, &bdev_path, &cow_path, &cache_size, &ignore_snap_errors);
if(ret) break;
ret = ioctl_reload_snap(minor, bdev_path, cow_path, cache_size, ignore_snap_errors);
if(ret) break;
break;
case IOCTL_RELOAD_INC:
//get params from user space
ret = get_reload_params((struct reload_params __user *)arg, &minor, &bdev_path, &cow_path, &cache_size, &ignore_snap_errors);
if(ret) break;
ret = ioctl_reload_inc(minor, bdev_path, cow_path, cache_size, ignore_snap_errors);
if(ret) break;
break;
case IOCTL_DESTROY:
//get minor from user space
ret = get_user(minor, (unsigned int __user *)arg);
if(ret){
LOG_ERROR(ret, "error copying minor number from user space");
break;
}
ret = ioctl_destroy(minor);
if(ret) break;
break;
case IOCTL_TRANSITION_INC:
//get minor from user space
ret = get_user(minor, (unsigned int __user *)arg);
if(ret){
LOG_ERROR(ret, "error copying minor number from user space");
break;
}
if (snap_devices[minor])
elastio_snap_wait_for_release(snap_devices[minor]);
ret = ioctl_transition_inc(minor);
if(ret) break;
break;
case IOCTL_TRANSITION_SNAP:
//get params from user space
ret = get_transition_snap_params((struct transition_snap_params __user *)arg, &minor, &cow_path, &fallocated_space);
if(ret) break;
ret = ioctl_transition_snap(minor, cow_path, fallocated_space);
if(ret) break;
break;
case IOCTL_RECONFIGURE:
//get params from user space
ret = get_reconfigure_params((struct reconfigure_params __user *)arg, &minor, &cache_size);
if(ret) break;
ret = ioctl_reconfigure(minor, cache_size);
if(ret) break;
break;
case IOCTL_ELASTIO_SNAP_INFO:
//get params from user space
info = kmalloc(sizeof(struct elastio_snap_info), GFP_KERNEL);
if(!info){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating memory for elastio-snap-info");
break;
}
ret = copy_from_user(info, (struct elastio_snap_info __user *)arg, sizeof(struct elastio_snap_info));
if(ret){
ret = -EFAULT;
LOG_ERROR(ret, "error copying elastio-snap-info struct from user space");
break;
}
ret = ioctl_elastio_snap_info(info);
if(ret) break;
ret = copy_to_user((struct elastio_snap_info __user *)arg, info, sizeof(struct elastio_snap_info));
if(ret){
ret = -EFAULT;
LOG_ERROR(ret, "error copying elastio-snap-info struct to user space");
break;
}
break;
case IOCTL_GET_FREE:
idx = get_free_minor();
if(idx < 0){
ret = idx;
LOG_ERROR(ret, "no free devices");
break;
}
ret = copy_to_user((int __user *)arg, &idx, sizeof(idx));
if(ret){
ret = -EFAULT;
LOG_ERROR(ret, "error copying minor to user space");
break;
}
break;
default:
ret = -EINVAL;
LOG_ERROR(ret, "invalid ioctl called");
break;
}
LOG_DEBUG("minor range = %u - %u", lowest_minor, highest_minor);
mutex_unlock(&ioctl_mutex);
if(bdev_path) kfree(bdev_path);
if(cow_path) kfree(cow_path);
if(info) kfree(info);
return ret;
}
/************************AUTOMATIC TRANSITION FUNCTIONS************************/
static void __tracer_active_to_dormant(struct snap_device *dev){
int ret;
//stop the cow thread
__tracer_destroy_cow_thread(dev);
//close the cow manager
ret = __tracer_destroy_cow_sync_and_close(dev);
if(ret) goto error;
//mark as dormant
smp_wmb();
clear_bit(ACTIVE, &dev->sd_state);
return;
error:
LOG_ERROR(ret, "error transitioning tracer to dormant state");
tracer_set_fail_state(dev, ret);
}
static void __tracer_unverified_snap_to_active(struct snap_device *dev, const char *user_mount_path, int is_user_space){
int ret;
unsigned int minor = dev->sd_minor;
char *cow_path;
char *bdev_path = dev->sd_bdev_path, *rel_path = dev->sd_cow_path;
unsigned long cache_size = dev->sd_cache_size;
//remove tracing while we setup the struct
if (!test_bit(TRACED, &dev->sd_state)) __tracer_destroy_tracing(dev);
//mark as active
set_bit(ACTIVE, &dev->sd_state);
clear_bit(UNVERIFIED, &dev->sd_state);
if (!test_bit(TRACED, &dev->sd_state)) {
bdev_path = dev->sd_bdev_path;
rel_path = dev->sd_cow_path;
dev->sd_bdev_path = NULL;
dev->sd_cow_path = NULL;
// setup base device
ret = __tracer_setup_base_dev(dev, bdev_path);
if (ret) goto error;
} else {
bdev_path = kstrdup(dev->sd_bdev_path, GFP_KERNEL);
if(!bdev_path){
ret = -ENOMEM;
goto error;
}
rel_path = kstrdup(dev->sd_cow_path, GFP_KERNEL);
if(!rel_path){
ret = -ENOMEM;
goto error;
}
}
// generate the full pathname
ret = user_mount_pathname_concat(user_mount_path, rel_path, &cow_path, is_user_space);
if (ret) goto error;
//setup the cow manager
ret = __tracer_setup_cow_reload_snap(dev, dev->sd_base_dev, cow_path, dev->sd_size, dev->sd_cache_size);
if(ret) goto error;
//setup the cow path
ret = __tracer_setup_cow_path(dev, dev->sd_cow->filp);
if(ret) goto error;
//setup the snapshot values
ret = __tracer_setup_snap(dev, minor, dev->sd_base_dev, dev->sd_size);
if(ret) goto error;
//setup the cow thread and run it
ret = __tracer_setup_snap_cow_thread(dev, minor);
if(ret) goto error;
wake_up_process(dev->sd_cow_thread);
//inject the tracing function
if (!test_bit(TRACED, &dev->sd_state)) {
ret = __tracer_setup_tracing(dev, minor);
if (ret) goto error;
}
kfree(bdev_path);
kfree(rel_path);
return;
error:
LOG_ERROR(ret, "error transitioning snapshot tracer to active state");
tracer_destroy(dev);
tracer_setup_unverified_snap(dev, minor, bdev_path, rel_path, cache_size, dev->sd_ignore_snap_errors);
tracer_set_fail_state(dev, ret);
kfree(bdev_path);
kfree(rel_path);
}
static void __tracer_unverified_inc_to_active(struct snap_device *dev, const char *user_mount_path, int is_user_space){
int ret;
unsigned int minor = dev->sd_minor;
char *cow_path;
char *bdev_path = dev->sd_bdev_path, *rel_path = dev->sd_cow_path;
unsigned long cache_size = dev->sd_cache_size;
//remove tracing while we setup the struct
if (!test_bit(TRACED, &dev->sd_state)) __tracer_destroy_tracing(dev);
cow_path = NULL;
bdev_path = NULL;
rel_path = NULL;
//mark as active
set_bit(ACTIVE, &dev->sd_state);
clear_bit(UNVERIFIED, &dev->sd_state);
//setup base device
if (!test_bit(TRACED, &dev->sd_state)) {
bdev_path = dev->sd_bdev_path;
rel_path = dev->sd_cow_path;
dev->sd_bdev_path = NULL;
dev->sd_cow_path = NULL;
// setup base device
ret = __tracer_setup_base_dev(dev, bdev_path);
if (ret) goto error;
} else {
bdev_path = kstrdup(dev->sd_bdev_path, GFP_KERNEL);
if(!bdev_path){
ret = -ENOMEM;
goto error;
}
rel_path = kstrdup(dev->sd_cow_path, GFP_KERNEL);
if(!rel_path){
ret = -ENOMEM;
goto error;
}
}
// generate the full pathname
ret = user_mount_pathname_concat(user_mount_path, rel_path, &cow_path, is_user_space);
if (ret) goto error;
//setup the cow manager
ret = __tracer_setup_cow_reload_inc(dev, dev->sd_base_dev, cow_path, dev->sd_size, dev->sd_cache_size);
if(ret) goto error;
//setup the cow path
ret = __tracer_setup_cow_path(dev, dev->sd_cow->filp);
if(ret) goto error;
//setup the cow thread and run it
ret = __tracer_setup_inc_cow_thread(dev, minor);
if(ret) goto error;
wake_up_process(dev->sd_cow_thread);
if (!test_bit(TRACED, &dev->sd_state)) {
// inject the tracing function
ret = __tracer_setup_tracing(dev, minor);
if (ret) goto error;
}
kfree(bdev_path);
kfree(rel_path);
return;
error:
LOG_ERROR(ret, "error transitioning incremental to active state");
tracer_destroy(dev);
tracer_setup_unverified_inc(dev, minor, bdev_path, rel_path, cache_size, dev->sd_ignore_snap_errors);
tracer_set_fail_state(dev, ret);
kfree(bdev_path);
kfree(rel_path);
}
static void __tracer_dormant_to_active(struct snap_device *dev, const char *user_mount_path, int is_user_space){
int ret;
char *cow_path;
// generate the full pathname
ret = user_mount_pathname_concat(user_mount_path, dev->sd_cow_path, &cow_path, is_user_space);
if (ret) goto error;
ret = __tracer_setup_cow_reopen(dev, dev->sd_base_dev, cow_path);
if(ret) goto error;
//restart the cow thread
if(test_bit(SNAPSHOT, &dev->sd_state)) ret = __tracer_setup_snap_cow_thread(dev, dev->sd_minor);
else ret = __tracer_setup_inc_cow_thread(dev, dev->sd_minor);
if(ret) goto error;
wake_up_process(dev->sd_cow_thread);
//set the state to active
smp_wmb();
set_bit(ACTIVE, &dev->sd_state);
clear_bit(UNVERIFIED, &dev->sd_state);
return;
error:
LOG_ERROR(ret, "error transitioning tracer to active state");
tracer_set_fail_state(dev, ret);
}
/************************AUTOMATIC HANDLER FUNCTIONS************************/
static void auto_transition_dormant(unsigned int i){
mutex_lock(&ioctl_mutex);
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_TRANSITION_DORMANT, 0);
#endif
__tracer_active_to_dormant(snap_devices[i]);
mutex_unlock(&ioctl_mutex);
}
static void auto_transition_active(unsigned int i, const char *dir_name, int is_user_space){
struct snap_device *dev = snap_devices[i];
mutex_lock(&ioctl_mutex);
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_TRANSITION_ACTIVE, 0);
#endif
if(test_bit(UNVERIFIED, &dev->sd_state)){
if(test_bit(SNAPSHOT, &dev->sd_state)) __tracer_unverified_snap_to_active(dev, dir_name, is_user_space);
else __tracer_unverified_inc_to_active(dev, dir_name, is_user_space);
}else __tracer_dormant_to_active(dev, dir_name, is_user_space);
mutex_unlock(&ioctl_mutex);
}
/***************************SYSTEM CALL HOOKING***************************/
static int __handle_bdev_mount_nowrite(const struct vfsmount *mnt, unsigned int *idx_out){
int ret;
unsigned int i;
struct snap_device *dev;
tracer_for_each(dev, i){
if(!dev || !test_bit(ACTIVE, &dev->sd_state) || tracer_read_fail_state(dev) || dev->sd_base_dev != mnt->mnt_sb->s_bdev) continue;
if (dev && dev->sd_cow && mnt == elastio_snap_get_mnt(dev->sd_cow->filp)) {
LOG_DEBUG("block device umount detected for device %d", i);
auto_transition_dormant(i);
ret = 0;
goto out;
}
}
i = 0;
ret = -ENODEV;
out:
*idx_out = i;
return ret;
}
static int __handle_bdev_mount_writable(const char *dir_name, const struct block_device *bdev, unsigned int *idx_out, int is_user_space){
int ret;
unsigned int i;
struct snap_device *dev;
struct bdev_container bd_c;
struct block_device *cur_bdev;
tracer_for_each(dev, i){
if(!dev || test_bit(ACTIVE, &dev->sd_state) || tracer_read_fail_state(dev)) continue;
if(test_bit(UNVERIFIED, &dev->sd_state)){
//get the block device for the unverified tracer we are looking into
cur_bdev = elastio_snap_blkdev_get_by_path(&bd_c, dev->sd_bdev_path, FMODE_READ, NULL);
if(IS_ERR(cur_bdev)){
cur_bdev = NULL;
continue;
}
//if the tracer's block device exists and matches the one being mounted perform transition
if(cur_bdev == bdev){
LOG_DEBUG("block device mount detected for unverified device %d", i);
auto_transition_active(i, dir_name, is_user_space);
elastio_snap_blkdev_put(&bd_c);
clear_bit(TRACED, &dev->sd_state);
ret = 0;
goto out;
}
//put the block device
elastio_snap_blkdev_put(&bd_c);
}else if(dev->sd_base_dev == bdev){
LOG_DEBUG("block device mount detected for dormant device %d", i);
auto_transition_active(i, dir_name, is_user_space);
clear_bit(TRACED, &dev->sd_state);
ret = 0;
goto out;
}
}
i = 0;
ret = -ENODEV;
out:
*idx_out = i;
return ret;
}
static int handle_bdev_mount_event(const char *dir_name, int follow_flags, unsigned int *idx_out, int mount_writable, int is_user_space){
int ret, lookup_flags = 0;
char *pathname = NULL;
struct path path = {};
struct block_device *bdev;
if(!(follow_flags & UMOUNT_NOFOLLOW)) lookup_flags |= LOOKUP_FOLLOW;
if (is_user_space) ret = user_path_at(0, dir_name, lookup_flags, &path);
else ret = kern_path(dir_name, lookup_flags, &path);
if(ret){
LOG_DEBUG("error finding path, %d", ret);
//error finding path
goto out;
}
LOG_DEBUG("path->dentry: %s, path->mnt->mnt_root: %s", path.dentry->d_name.name, path.mnt->mnt_root->d_name.name);
if(path.dentry != path.mnt->mnt_root){
//path specified isn't a mount point
LOG_DEBUG("path specified isn't a mount point for dir name");
ret = -ENODEV;
goto out;
}
bdev = path.mnt->mnt_sb->s_bdev;
if(!bdev){
//path specified isn't mounted on a block device
LOG_DEBUG("path specified isn't mounted on a block device");
ret = -ENODEV;
goto out;
}
if(!mount_writable) ret = __handle_bdev_mount_nowrite(path.mnt, idx_out);
else ret = __handle_bdev_mount_writable(dir_name, bdev, idx_out, is_user_space);
if(ret){
//no block device found that matched an incremental
goto out;
}
kfree(pathname);
path_put(&path);
return 0;
out:
if(pathname) kfree(pathname);
path_put(&path);
*idx_out = 0;
return ret;
}
static int __maybe_unused setup_traced(const char *dev_name)
{
int ret, i, len;
char *upath = NULL;
struct snap_device *dev = NULL;
LOG_DEBUG("enter setup_traced");
unsigned int minor = 0;
char *bdev_path = NULL;
unsigned long cache_size = 0;
ret = pathname_to_absolute(dev_name, &upath, &len);
if(!upath || ret) goto out;
LOG_DEBUG("absolute path: %s(%s), len: %d", upath, dev_name, len);
tracer_for_each(dev, i) {
if(!dev || test_bit(SNAPSHOT, &dev->sd_state) || !test_bit(UNVERIFIED, &dev->sd_state) || tracer_read_fail_state(dev) || upath[0] != '/') continue;
LOG_DEBUG("dev->sd_bdev_path : %s", dev->sd_bdev_path);
if (memcmp(dev->sd_bdev_path, upath, min((int)strlen(dev->sd_bdev_path), len)) != 0) {
continue;
}
LOG_DEBUG("found block device in setup_traced");
minor = dev->sd_minor;
cache_size = dev->sd_cache_size;
bdev_path = dev->sd_bdev_path; // moved
// dev->sd_bdev_path = NULL; // concurrent mount may read this value
ret = __tracer_setup_base_dev(dev, dev_name);
if(ret) goto error;
set_bit(TRACED, &dev->sd_state);
#ifndef USE_BDOPS_SUBMIT_BIO
LOG_DEBUG("setting find_orig_mrf in setup_traced");
ret = find_orig_mrf(dev->sd_base_dev, &dev->sd_orig_mrf);
if (ret) goto error;
// inject traing mrf
dev->sd_base_dev->bd_disk->queue->make_request_fn = tracing_mrf;
#else
LOG_DEBUG("find and set bdops in setup_traced");
if(!dev->sd_tracing_ops){
ret = find_orig_fops(dev->sd_base_dev, &dev->sd_orig_ops, &dev->sd_orig_mrf, &dev->sd_tracing_ops);
if(ret) goto error;
if(!dev->sd_tracing_ops){
LOG_DEBUG("allocating block_device_operations with submit_bio replaced by our tracing function");
ret=tracing_ops_alloc(dev);
if(ret){
goto error;
}
}else{
LOG_DEBUG("using already existing tracing_ops");
}
dev->sd_base_dev->bd_disk->fops = dev->sd_tracing_ops->bd_ops;
#ifdef HAVE_BD_HAS_SUBMIT_BIO
dev->sd_base_dev->bd_has_submit_bio = true; // kernel version >= 6.4
#elif defined HAVE_BD_HAS_SUBMIT_BIO_FLAGS
bdev_set_flag(dev->sd_base_dev, BD_HAS_SUBMIT_BIO);
#endif
}
else {
LOG_DEBUG("device with minor %i already has sd_tracing_ops", i);
}
#endif
break;
}
out:
LOG_DEBUG("exit setup_traced");
if(upath) kfree(upath);
if(bdev_path) kfree(bdev_path);
return 0;
error:
LOG_ERROR(ret, "error in setup_traced");
if(upath) kfree(upath);
if(dev && dev->sd_base_dev) __tracer_destroy_base_dev(dev);
if(dev) dev->sd_orig_mrf = NULL;
if(dev) clear_bit(TRACED, &dev->sd_state);
if(dev) {
char *cow_path = dev->sd_cow_path;
tracer_setup_unverified_inc(dev, minor, bdev_path, cow_path, cache_size, dev->sd_ignore_snap_errors);
tracer_set_fail_state(dev, ret);
kfree(cow_path);
}
if(bdev_path) kfree(bdev_path);
return ret;
}
#define handle_bdev_mount_nowrite_kernel(dir_name, follow_flags, idx_out) handle_bdev_mount_event(dir_name, follow_flags, idx_out, 0, 0)
#define handle_bdev_mount_nowrite_user(dir_name, follow_flags, idx_out) handle_bdev_mount_event(dir_name, follow_flags, idx_out, 0, 1)
#define handle_bdev_mounted_writable_kernel(dir_name, idx_out) handle_bdev_mount_event(dir_name, 0, idx_out, 1, 0)
#define handle_bdev_mounted_writable_user(dir_name, idx_out) handle_bdev_mount_event(dir_name, 0, idx_out, 1, 1)
static void post_umount_check(int dormant_ret, long umount_ret, unsigned int idx, const char *dir_name, bool is_user_space){
struct bdev_container bd_c;
struct block_device *bdev;
struct snap_device *dev;
struct super_block *sb;
//if we didn't do anything or failed, just return
if(dormant_ret) return;
dev = snap_devices[idx];
//if we successfully went dormant, but the umount call failed, reactivate
if(umount_ret){
bdev = elastio_snap_blkdev_get_by_path(&bd_c, dev->sd_bdev_path, FMODE_READ, NULL);
if(IS_ERR(bdev)){
LOG_DEBUG("device gone, moving to error state");
tracer_set_fail_state(dev, -ENODEV);
return;
}
elastio_snap_blkdev_put(&bd_c);
LOG_DEBUG("umount call failed, reactivating tracer %u", idx);
auto_transition_active(idx, dir_name, is_user_space);
return;
}
//force the umount operation to complete synchronously
task_work_flush();
//if we went dormant, but the block device is still mounted somewhere, goto fail state
sb = elastio_snap_get_super(dev->sd_base_dev);
if(sb){
if(!(sb->s_flags & MS_RDONLY)){
LOG_ERROR(-EIO, "device still mounted after umounting cow file's file-system. entering error state");
tracer_set_fail_state(dev, -EIO);
drop_super(sb);
return;
}
drop_super(sb);
}
LOG_DEBUG("post umount check succeeded");
}
// HOOKS DEFINITION
#include "extract_mount_params.h"
#if !SYS_MOUNT_ADDR
#if __X64_SYS_MOUNT_ADDR || __ARM64_SYS_MOUNT_ADDR
#define USE_ARCH_MOUNT_FUNCS
#else
#warning "No mount function found"
#endif
#endif
#ifdef USE_ARCH_MOUNT_FUNCS
#ifdef USE_NEW_MOUNT_API
// kernel >= 6.6
static asmlinkage long (*orig_move_mount)(struct pt_regs *regs);
static asmlinkage long (*orig_mount_setattr)(struct pt_regs *regs);
static asmlinkage long (*orig_fsconfig)(struct pt_regs *regs);
struct mnt_namespace {
struct ns_common ns;
struct mount * root;
#ifndef HAVE_BDEV_FREEZE
// kernel < 6.8
/*
* Traversal and modification of .list is protected by either
* - taking namespace_sem for write, OR
* - taking namespace_sem for read AND taking .ns_lock.
*/
struct list_head list;
spinlock_t ns_lock;
unsigned int mounts; /* # of mounts in the namespace */
#else
// kernel >= 6.8
struct rb_root mounts; /* Protected by namespace_sem */
unsigned int nr_mounts; /* # of mounts in the namespace */
#endif
struct user_namespace *user_ns;
struct ucounts *ucounts;
u64 seq; /* Sequence number to prevent loops */
wait_queue_head_t poll;
u64 event;
unsigned int pending_mounts;
} __randomize_layout;
struct mount {
struct hlist_node mnt_hash;
struct mount *mnt_parent;
struct dentry *mnt_mountpoint;
struct vfsmount mnt;
union {
#if defined(HAVE_BDEV_FREEZE) && !defined(MNT_ONRB)
struct rb_node mnt_node; /* node in the ns->mounts rbtree */
#endif
struct rcu_head mnt_rcu;
struct llist_node mnt_llist;
};
#ifdef CONFIG_SMP
struct mnt_pcp __percpu *mnt_pcp;
#else
int mnt_count;
int mnt_writers;
#endif
struct list_head mnt_mounts; /* list of children, anchored here */
struct list_head mnt_child; /* and going through their mnt_child */
struct list_head mnt_instance; /* mount instance on sb->s_mounts */
const char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */
#ifndef HAVE_BDEV_FREEZE
// kernel < 6.8
struct list_head mnt_list;
#elif defined MNT_ONRB
// kernel >= 6.8
union {
struct rb_node mnt_node; /* Under ns->mounts */
struct list_head mnt_list;
};
#else
// kernel >= 6.13
struct list_head mnt_list;
#endif
struct list_head mnt_expire; /* link in fs-specific expiry list */
struct list_head mnt_share; /* circular list of shared mounts */
struct list_head mnt_slave_list;/* list of slave mounts */
struct list_head mnt_slave; /* slave list entry */
struct mount *mnt_master; /* slave is on master->mnt_slave_list */
struct mnt_namespace *mnt_ns; /* containing namespace */
struct mountpoint *mnt_mp; /* where is it mounted */
union {
struct hlist_node mnt_mp_list; /* list mounts with the same mountpoint */
struct hlist_node mnt_umount;
};
struct list_head mnt_umounting; /* list entry for umount propagation */
#ifdef CONFIG_FSNOTIFY
struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
__u32 mnt_fsnotify_mask;
#endif
int mnt_id; /* mount identifier */
int mnt_group_id; /* peer group identifier */
int mnt_expiry_mark; /* true if marked for expiry */
struct hlist_head mnt_pins;
struct hlist_head mnt_stuck_children;
} __randomize_layout;
#else
static asmlinkage long (*orig_mount)(struct pt_regs *regs);
#endif //USE_NEW_MOUNT_API
static asmlinkage long (*orig_umount)(struct pt_regs *regs);
#else
#ifndef USE_NEW_MOUNT_API
static asmlinkage long (*orig_mount)(char __user *, char __user *, char __user *, unsigned long, void __user *);
#endif //USE_NEW_MOUNT_API
static asmlinkage long (*orig_umount)(char __user *name, int flags);
#endif
#ifdef HAVE_SYS_OLDUMOUNT
static asmlinkage long (*orig_oldumount)(char __user *);
#endif
// MOUNT HOOKS
#ifndef USE_NEW_MOUNT_API
// kernel < 6.6
#ifdef USE_ARCH_MOUNT_FUNCS
static asmlinkage long mount_hook(struct pt_regs *regs){
#else
static asmlinkage long mount_hook(char __user *dev_name, char __user *dir_name, char __user *type, unsigned long flags, void __user *data){
#endif
int ret;
int ret_dev;
int ret_dir;
long sys_ret;
unsigned int idx;
char *buff_dev_name = NULL;
char *buff_dir_name = NULL;
unsigned long real_flags;
#ifdef USE_ARCH_MOUNT_FUNCS
unsigned long flags;
char *dir_name;
char *dev_name;
ret = mount_hook_extract_params(regs, &dev_name, &dir_name, &flags);
if (ret) {
// should never happen
LOG_ERROR(ret, "couldn't extract mount params");
return ret;
}
#endif
real_flags = flags;
buff_dev_name = kmalloc(PATH_MAX, GFP_ATOMIC);
buff_dir_name = kmalloc(PATH_MAX, GFP_ATOMIC);
if(!buff_dev_name || !buff_dir_name) {
if(buff_dev_name)
kfree(buff_dev_name);
if(buff_dir_name)
kfree(buff_dir_name);
return -ENOMEM;
}
ret_dev = copy_from_user(buff_dev_name, dev_name, PATH_MAX);
ret_dir = copy_from_user(buff_dir_name, dir_name, PATH_MAX);
if(ret_dev || ret_dir)
LOG_DEBUG("detected block device Get mount params error!, %d, %d, 0x%lx", ret_dev, ret_dir, flags);
else
LOG_DEBUG("detected block device mount: %s -> %s : 0x%lx", buff_dev_name,
buff_dir_name, real_flags);
//get rid of the magic value if its present
if((real_flags & MS_MGC_MSK) == MS_MGC_VAL) real_flags &= ~MS_MGC_MSK;
if(real_flags & (MS_BIND | MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE | MS_MOVE) || ((real_flags & MS_RDONLY) && !(real_flags & MS_REMOUNT))){
//bind, shared, move, or new read-only mounts it do not affect the state of the driver
#ifdef USE_ARCH_MOUNT_FUNCS
sys_ret = orig_mount(regs);
#else
sys_ret = orig_mount(dev_name, dir_name, type, flags, data);
#endif
} else if((real_flags & MS_RDONLY) && (real_flags & MS_REMOUNT)){
LOG_DEBUG("detected remount to read-only mount");
ret = handle_bdev_mount_nowrite_user(dir_name, 0, &idx);
#ifdef USE_ARCH_MOUNT_FUNCS
sys_ret = orig_mount(regs);
#else
sys_ret = orig_mount(dev_name, dir_name, type, flags, data);
#endif
post_umount_check(ret, sys_ret, idx, dir_name, 1);
} else{
//new read-write mount
ret = setup_traced(buff_dev_name);
if(ret) {
LOG_ERROR(ret, "failed to setup traced");
}
#ifdef USE_ARCH_MOUNT_FUNCS
sys_ret = orig_mount(regs);
#else
sys_ret = orig_mount(dev_name, dir_name, type, flags, data);
#endif
if(!sys_ret) handle_bdev_mounted_writable_user(dir_name, &idx);
}
kfree(buff_dev_name);
kfree(buff_dir_name);
LOG_DEBUG("mount returned: %ld", sys_ret);
return sys_ret;
}
#else
// kernel >= 6.6
#ifndef HAVE_BDEV_FREEZE
// kernel <= 6.8
static bool may_mount(void)
{
return ns_capable(current->nsproxy->mnt_ns->user_ns, CAP_SYS_ADMIN);
}
#endif
static asmlinkage long move_mount_hook(struct pt_regs *regs){
int ret;
int ret_from;
int ret_to;
long sys_ret;
unsigned int idx;
char *buff_to_dir_name = NULL;
char *buff_from_dir_name = NULL;
unsigned int real_flags;
struct path from_path;
unsigned int lflags;
int from_dfd;
char *from_dir;
int to_dfd;
char *to_dir;
unsigned int flags;
ret = move_mount_hook_extract_params(regs, &from_dfd, &from_dir, &to_dfd, &to_dir, &flags);
if (ret) {
// should never happen
LOG_ERROR(ret, "couldn't extract move mount params");
return ret;
}
LOG_DEBUG("detected block device move mount hooked: 0x%x", flags);
real_flags = flags;
#ifndef HAVE_BDEV_FREEZE
if (!may_mount()) return -EPERM;
#endif
if (real_flags & ~MOVE_MOUNT__MASK) return -EINVAL;
if ((real_flags & (MOVE_MOUNT_BENEATH | MOVE_MOUNT_SET_GROUP)) == (MOVE_MOUNT_BENEATH | MOVE_MOUNT_SET_GROUP))
return -EINVAL;
lflags = 0;
if (real_flags & MOVE_MOUNT_F_SYMLINKS) lflags |= LOOKUP_FOLLOW;
if (real_flags & MOVE_MOUNT_F_AUTOMOUNTS) lflags |= LOOKUP_AUTOMOUNT;
if (real_flags & MOVE_MOUNT_F_EMPTY_PATH) lflags |= LOOKUP_EMPTY;
buff_from_dir_name = kmalloc(PATH_MAX, GFP_ATOMIC);
buff_to_dir_name = kmalloc(PATH_MAX, GFP_ATOMIC);
if (!buff_from_dir_name || !buff_to_dir_name) {
if(buff_from_dir_name) kfree(buff_from_dir_name);
if(buff_to_dir_name) kfree(buff_to_dir_name);
return -ENOMEM;
}
ret_to = copy_from_user(buff_to_dir_name, to_dir, PATH_MAX);
ret_from = copy_from_user(buff_from_dir_name, from_dir, PATH_MAX);
if(ret_to || ret_from) LOG_DEBUG("detected block device Get mount params error!");
else LOG_DEBUG("from dir %s to dir %s", buff_from_dir_name, buff_to_dir_name);
// move mount from dir is not empty means it's not a new mount
if (*buff_from_dir_name != '\0') {
kfree(buff_to_dir_name);
kfree(buff_from_dir_name);
goto call_orig;
}
kfree(buff_to_dir_name);
kfree(buff_from_dir_name);
ret = user_path_at(from_dfd, from_dir, lflags, &from_path);
// make sure we can get the path & its block device normally
if(ret < 0 || !from_path.mnt || !from_path.mnt->mnt_sb || !from_path.mnt->mnt_sb->s_bdev) goto free_path;
struct block_device *bdev = from_path.mnt->mnt_sb->s_bdev;
LOG_DEBUG("from_path->dentry: %s, from_path->mnt->mnt_root: %s", from_path.dentry->d_name.name, from_path.mnt->mnt_root->d_name.name);
// handling the case like: mount --bind foo foo, where foo is a directory (not a block device)
if (from_path.mnt->mnt_root->d_name.name[0] != '/') {
LOG_DEBUG("from_path.mnt->mnt_root->d_name.name[0] != '/'");
goto free_path;
}
// handling the new read only mount
LOG_DEBUG("s_flags & SB_RDONLY : %lu", from_path.mnt->mnt_sb->s_flags & SB_RDONLY);
if (sb_rdonly(from_path.mnt->mnt_sb)){
LOG_DEBUG("from_path.mnt->mnt_sb is read only");
goto free_path;
}
char *dev_name = kmalloc(PATH_MAX, GFP_ATOMIC);
if(!dev_name) {
LOG_ERROR(-ENOMEM, "failed to allocate memory for dev_name");
goto free_path;
}
LOG_DEBUG("bdevname %pg", bdev);
snprintf(dev_name, PATH_MAX, "/dev/%pg", bdev);
// bdev_name will be like sdb1, we want to make it look like /dev/sdb1
LOG_DEBUG("dev_name %s", dev_name);
// new read-write mount
ret = setup_traced(dev_name);
if(ret) {
LOG_ERROR(ret, "failed to setup traced");
}
sys_ret = orig_move_mount(regs);
if(!sys_ret) handle_bdev_mounted_writable_user(to_dir, &idx);
path_put(&from_path);
kfree(dev_name);
LOG_DEBUG("EXIT move_mount_hook");
return sys_ret;
free_path:
path_put(&from_path);
call_orig:
sys_ret = orig_move_mount(regs);
return sys_ret;
}
static asmlinkage long mount_setattr_hook(struct pt_regs *regs){
int ret;
long sys_ret;
unsigned int idx;
unsigned int real_flags;
struct mount_attr attr;
struct path target;
unsigned int lookup_flags = LOOKUP_AUTOMOUNT | LOOKUP_FOLLOW;
char *dir_name = NULL;
int len_res;
int dfd;
char *path;
unsigned int flags;
struct mount_attr *uattr;
size_t usize;
ret = mount_setattr_hook_extract_params(regs, &dfd, &path, &flags, &uattr, &usize);
if (ret) {
// should never happen
LOG_ERROR(ret, "couldn't extract mount setattr params");
return ret;
}
real_flags = flags;
LOG_DEBUG("detected block device mount setattr hooks , flags : 0x%x", flags);
if (real_flags == 0) goto call_orig;
if (real_flags & ~(AT_EMPTY_PATH | AT_RECURSIVE | AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT)) return -EINVAL;
if (unlikely(usize > PAGE_SIZE)) return -E2BIG;
if (unlikely(usize < MOUNT_ATTR_SIZE_VER0)) return -EINVAL;
#ifndef HAVE_BDEV_FREEZE
if (!may_mount()) return -EPERM;
#endif
ret = copy_struct_from_user(&attr, sizeof(attr), uattr, usize);
if (ret){
LOG_ERROR(ret, "error in copying mount attr from user");
return ret;
}
/* Don't bother walking through the mounts if this is a nop. */
if (attr.attr_set == 0 && attr.attr_clr == 0 && attr.propagation == 0) return 0;
// not remount && read only
LOG_DEBUG("attr.attr_set = %llu, attr.attr_clr = %llu, attr.propagation = %llu", attr.attr_set, attr.attr_clr, attr.propagation);
// set propagation flags (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE) will not effect driver status
if(attr.propagation && (attr.propagation & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))) {
LOG_DEBUG("propagation get one of (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE)");
goto call_orig;
}
// new read only mount just ignroe it
if (attr.attr_set == MOUNT_ATTR_RDONLY && attr.attr_clr == 0) {
LOG_DEBUG("its a new read only mount just ignroe it");
goto call_orig;
}
if (real_flags & AT_NO_AUTOMOUNT) lookup_flags &= ~LOOKUP_AUTOMOUNT;
if (real_flags & AT_SYMLINK_NOFOLLOW) lookup_flags &= ~LOOKUP_FOLLOW;
if (real_flags & AT_EMPTY_PATH) lookup_flags |= LOOKUP_EMPTY;
ret = user_path_at(dfd, path, lookup_flags, &target);
if (ret < 0) {
LOG_DEBUG("error finding path in mount_setattr_hook");
goto free_path;
}
ret = path_get_absolute_pathname(&target, &dir_name, &len_res);
if (ret){
LOG_ERROR(ret, "error converting target to absolute pathname");
goto free_path;
}
LOG_DEBUG("dir_name = %s", dir_name);
LOG_DEBUG("path->dentry: %s, path->mnt->mnt_root: %s", target.dentry->d_name.name, target.mnt->mnt_root->d_name.name);
if (attr.attr_clr != 0 && attr.attr_set == MOUNT_ATTR_RDONLY){
// remount with readonly
LOG_DEBUG("this is a read only remount !!!");
} else {
// no propagation, no remount with readonly --> remount with rw, we should treat it as mount
LOG_DEBUG("detect r/w remount in mount_setattr_hook !!!");
struct block_device *bdev = target.mnt->mnt_sb->s_bdev;
if (!bdev || !bdev->bd_disk) goto free_path;
char *dev_name = kmalloc(PATH_MAX, GFP_ATOMIC);
if(!dev_name) {
LOG_ERROR(-ENOMEM, "failed to allocate memory for dev_name");
goto free_path;
}
LOG_DEBUG("bdevname %pg", bdev);
snprintf(dev_name, PATH_MAX, "/dev/%pg", bdev);
// bdev_name will be like sdb1, we want to make it look like /dev/sdb1
LOG_DEBUG("dev_name %s", dev_name);
// new read-write mount
ret = setup_traced(dev_name);
if(ret) {
LOG_ERROR(ret, "failed to setup traced");
}
sys_ret = orig_mount_setattr(regs);
if(!sys_ret) handle_bdev_mounted_writable_kernel(dir_name, &idx);
kfree(dev_name);
}
// clean resoure
path_put(&target);
LOG_DEBUG("EXIT mount_setattr_hook");
return sys_ret;
free_path:
path_put(&target);
call_orig:
sys_ret = orig_mount_setattr(regs);
return sys_ret;
}
#ifndef HAVE_BDEV_FREEZE
// kernel < 6.8
static inline void lock_ns_list(struct mnt_namespace *ns)
{
spin_lock(&ns->ns_lock);
}
static inline void unlock_ns_list(struct mnt_namespace *ns)
{
spin_unlock(&ns->ns_lock);
}
#endif // HAVE_BDEV_FREEZE
static asmlinkage long fsconfig_hook(struct pt_regs *regs){
int ret;
long sys_ret;
unsigned int idx;
struct fs_context *fc;
struct fd f;
struct super_block *sb;
bool remount_ro = false;
struct mnt_namespace *ns;
struct mount *mnt = NULL;
#ifdef HAVE_BDEV_FREEZE
struct mount *n = NULL;
#endif
struct dentry *target_dentry = NULL;
struct path path = {};
char *dir_name = NULL;
int len_res;
int fd;
unsigned int cmd;
char *key;
void *value;
int aux;
ret = fsconfig_hook_extract_params(regs, &fd, &cmd, &key, &value, &aux);
if (ret) {
// should never happen
LOG_ERROR(ret, "couldn't extract fsconfig params");
return ret;
}
LOG_DEBUG("detected fsconfig hooked: cmd : 0x%x", cmd);
// we only care about remount,ro case
if (fd < 0) return -EINVAL;
if (cmd != FSCONFIG_CMD_RECONFIGURE) goto call_orig;
if (key || value || aux) return -EINVAL;
LOG_DEBUG("fsconfig is FSCONFIG_CMD_RECONFIGUR (remount)");
f = fdget(fd);
#ifdef fd_file
if (!fd_file(f)) {
LOG_DEBUG("f.file is null");
return -EBADF;
}
fc = fd_file(f)->private_data;
#else
if (!f.file) {
LOG_DEBUG("f.file is null");
return -EBADF;
}
fc = f.file->private_data;
#endif // fd_file
if (fc->phase != FS_CONTEXT_RECONF_PARAMS) goto out_f;
sb = fc->root->d_sb;
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) {
fc->phase = FS_CONTEXT_FAILED;
goto out_f;
}
if (fc->sb_flags_mask & ~MS_RMT_MASK) goto out_f;
if (sb->s_writers.frozen != SB_UNFROZEN) goto out_f;
if (fc->sb_flags_mask & SB_RDONLY) {
#ifdef CONFIG_BLOCK
if (!(fc->sb_flags & SB_RDONLY) && sb->s_bdev &&
bdev_read_only(sb->s_bdev))
goto out_f;
#endif
remount_ro = (fc->sb_flags & SB_RDONLY) && !sb_rdonly(sb);
}
if (!remount_ro) goto out_f;
ns = current->nsproxy->mnt_ns;
LOG_DEBUG("start to find fs's mount point in mnt_namespace list");
#if NAMESPACE_SEM_ADDR
down_read((struct rw_semaphore *)(NAMESPACE_SEM_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)));
#endif
#ifndef HAVE_BDEV_FREEZE
lock_ns_list(ns);
list_for_each_entry(mnt, &ns->list, mnt_list) {
#else
rbtree_postorder_for_each_entry_safe(mnt, n, &ns->mounts, mnt_node) {
#endif
if (!mnt || !mnt->mnt.mnt_root) LOG_WARN("found a NULL mount/vfsmount in mountpoint list");
else if (fc->root == mnt->mnt.mnt_root) {
LOG_DEBUG("found a filesystem root dentry in mountpoint list");
target_dentry = mnt->mnt.mnt_root;
path.dentry = target_dentry;
path.mnt = &mnt->mnt;
break;
}
}
#ifndef HAVE_BDEV_FREEZE
unlock_ns_list(ns);
#endif
#if NAMESPACE_SEM_ADDR
up_read((struct rw_semaphore *)(NAMESPACE_SEM_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)));
#endif
if(!target_dentry) {
LOG_ERROR(-ENOENT, "failed to find the dentry in current mount namespace");
goto out_f;
}
ret = path_get_absolute_pathname(&path, &dir_name, &len_res);
if (ret){
LOG_ERROR(ret, "error converting target to absolute pathname");
goto out_f;
}
LOG_DEBUG("dir_name = %s, len_res = %d", dir_name, len_res);
ret = handle_bdev_mount_nowrite_kernel(dir_name, 0, &idx);
sys_ret = orig_fsconfig(regs);
post_umount_check(ret, sys_ret, idx, dir_name, 0);
LOG_DEBUG("origin fsconfig returned: %ld", sys_ret);
fdput(f);
return sys_ret;
out_f:
fdput(f);
call_orig:
sys_ret = orig_fsconfig(regs);
return sys_ret;
}
#endif // USE_NEW_MOUNT_API
#ifdef USE_ARCH_MOUNT_FUNCS
static asmlinkage long umount_hook(struct pt_regs *regs){
#else
static asmlinkage long umount_hook(char __user *name, int flags){
#endif
int ret;
long sys_ret;
unsigned int idx;
char* buff_dev_name = NULL;
#ifdef USE_ARCH_MOUNT_FUNCS
unsigned long flags;
char *name;
ret = umount_hook_extract_params(regs, &name, &flags);
if (ret) {
// should never happen
LOG_ERROR(ret, "couldn't extract umount params");
return ret;
}
#endif
buff_dev_name = kmalloc(PATH_MAX, GFP_ATOMIC);
if(!buff_dev_name) {
return -ENOMEM;
}
ret = copy_from_user(buff_dev_name, name, PATH_MAX);
if(ret)
LOG_DEBUG("detected block device umount error: %d", ret);
else
LOG_DEBUG("detected block device umount: %s : %ld", buff_dev_name, (unsigned long) flags);
kfree(buff_dev_name);
ret = handle_bdev_mount_nowrite_user(name, flags, &idx);
#ifdef USE_ARCH_MOUNT_FUNCS
sys_ret = orig_umount(regs);
#else
sys_ret = orig_umount(name, flags);
#endif
post_umount_check(ret, sys_ret, idx, name, 1);
LOG_DEBUG("umount returned: %ld", sys_ret);
return sys_ret;
}
#ifdef HAVE_SYS_OLDUMOUNT
static asmlinkage long oldumount_hook(char __user *name){
int ret;
long sys_ret;
unsigned int idx;
char* buff_dev_name = NULL;
buff_dev_name = kmalloc(PATH_MAX, GFP_ATOMIC);
if(!buff_dev_name) {
return -ENOMEM;
}
ret=copy_from_user(buff_dev_name, name, PATH_MAX);
if(ret)
LOG_DEBUG("detected block device oldumount error:%d", ret);
else
LOG_DEBUG("detected block device oldumount: %s", name);
kfree(buff_dev_name);
ret = handle_bdev_mount_nowrite_user(name, 0, &idx);
sys_ret = orig_oldumount(name);
post_umount_check(ret, sys_ret, idx, name, 1);
LOG_DEBUG("oldumount returned: %ld", sys_ret);
return sys_ret;
}
#endif
// SYSCALL_TABLE_HOOKING
#if !X64_SYS_CALL_ADDR
static void **system_call_table = NULL;
static void **find_sys_call_table(void){
#ifdef USE_NEW_MOUNT_API
// kernel 6.6
long long move_mount_address = 0;
long long mount_setattr_address = 0;
long long fsconfig_address = 0;
#else
long long mount_address = 0;
#endif
long long umount_address = 0;
long long offset = 0;
void **sct;
if(!SYS_CALL_TABLE_ADDR)
return NULL;
// On kernels after 4.9+, sys_mount() & sys_umount()
// have been switched to the architecture-dependent
// functions, f.e., __x86_64_sys_mount() or __arm64_sys_umount()
// These functions use 'struct pt_regs *' as a parameter.
// Hence, we added additional define USE_ARCH_MOUNT_FUNCS
// to support mount hooks on different kernels
#if !defined(USE_ARCH_MOUNT_FUNCS) && !defined(USE_NEW_MOUNT_API)
// USE_ARCH_MOUNT_FUNCS and kernel < 6.6
mount_address = SYS_MOUNT_ADDR;
umount_address = SYS_UMOUNT_ADDR;
#else
#if __X64_SYS_MOUNT_ADDR
#ifdef USE_NEW_MOUNT_API
// kernel >= 6.6
move_mount_address = __X64_SYS_MOVE_MOUNT_ADDR;
mount_setattr_address = __X64_SYS_MOUNT_SETATTR_ADDR;
fsconfig_address = __X64_SYS_FSCONFIG_ADDR;
#else // USE_NEW_MOUNT_API
mount_address = __X64_SYS_MOUNT_ADDR;
#endif // USE_NEW_MOUNT_API
umount_address = __X64_SYS_UMOUNT_ADDR;
#elif __ARM64_SYS_MOUNT_ADDR
#ifdef USE_NEW_MOUNT_API
// kernel >= 6.6
move_mount_address = __ARM64_SYS_MOVE_MOUNT_ADDR;
mount_setattr_address = __ARM64_SYS_MOUNT_SETATTR_ADDR;
fsconfig_address = __ARM64_SYS_FSCONFIG_ADDR;
#else
mount_address = __ARM64_SYS_MOUNT_ADDR;
#endif
umount_address = __ARM64_SYS_UMOUNT_ADDR;
#else
#error "Architecture not supported"
#endif
#endif
#ifdef USE_NEW_MOUNT_API
// kernel >= 6.6
if (!move_mount_address || !mount_setattr_address || !umount_address || !fsconfig_address)
return NULL;
#else
if (!mount_address || !umount_address)
return NULL;
#endif // USE_NEW_MOUNT_API
offset = ((void *)kfree) - (void *)KFREE_ADDR;
sct = (void **)SYS_CALL_TABLE_ADDR + offset / sizeof(void **);
#ifdef USE_NEW_MOUNT_API
// kernel >= 6.6
if(sct[__NR_move_mount] != (void **)move_mount_address + offset / sizeof(void **)) return NULL;
if(sct[__NR_mount_setattr] != (void **)mount_setattr_address + offset / sizeof(void **)) return NULL;
if(sct[__NR_fsconfig] != (void **)fsconfig_address + offset / sizeof(void **)) return NULL;
#else
if(sct[__NR_mount] != (void **)mount_address + offset / sizeof(void **)) return NULL;
#endif // USE_NEW_MOUNT_API
if(sct[__NR_umount2] != (void **)umount_address + offset / sizeof(void **)) return NULL;
#ifdef HAVE_SYS_OLDUMOUNT
if(sct[__NR_umount] != (void **)SYS_OLDUMOUNT_ADDR + offset / sizeof(void **)) return NULL;
#endif
LOG_DEBUG("system call table located at 0x%p", sct);
return sct;
}
#ifdef CONFIG_ARM64
static int set_page_rw(unsigned long addr)
{
int (*__change_memory_common)(unsigned long, unsigned long,
pgprot_t, pgprot_t) = (__CHANGE_MEMORY_COMMON_ADDR != 0) ?
(int (*)(unsigned long, unsigned long, pgprot_t, pgprot_t)) (__CHANGE_MEMORY_COMMON_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
if (!__change_memory_common) {
LOG_ERROR(-EFAULT, "error getting __change_memory_common address");
return -EFAULT;
}
vm_unmap_aliases();
return __change_memory_common(addr, PAGE_SIZE, __pgprot(PTE_WRITE), __pgprot(PTE_RDONLY));
}
static int set_page_ro(unsigned long addr)
{
int (*__change_memory_common)(unsigned long, unsigned long,
pgprot_t, pgprot_t) = (__CHANGE_MEMORY_COMMON_ADDR != 0) ?
(int (*)(unsigned long, unsigned long, pgprot_t, pgprot_t)) (__CHANGE_MEMORY_COMMON_ADDR + (long long)(((void *)kfree) - (void *)KFREE_ADDR)) : NULL;
if (!__change_memory_common) {
LOG_ERROR(-EFAULT, "error getting __change_memory_common address");
return -EFAULT;
}
vm_unmap_aliases();
return __change_memory_common(addr, PAGE_SIZE, __pgprot(PTE_RDONLY), __pgprot(PTE_WRITE));
}
#endif
#ifdef CONFIG_X86_64
#ifndef X86_CR0_WP
#define X86_CR0_WP (1UL << 16)
#endif
static inline void wp_cr0(unsigned long cr0) {
// Enabling/disabling approach with usage of the write_cr0 function stopped to work somewhere starting from the kernels 5.X (maybe 5.3)
// after this patch https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8dbec27a242cd3e2816eeb98d3237b9f57cf6232
// This is a simple workaround
__asm__ __volatile__ ("mov %0, %%cr0": "+r" (cr0));
}
static inline unsigned long disable_page_protection(void) {
unsigned long cr0;
cr0 = read_cr0();
wp_cr0(cr0 & ~X86_CR0_WP);
return cr0;
}
static inline void reenable_page_protection(unsigned long cr0) {
wp_cr0(cr0);
}
#endif // CONFIG_X86_64
/** generic function to set a system call table to a read-write mode */
static inline int syscall_mode_rw(void **syscall_table, int syscall_num, unsigned long *flags)
{
if (!flags) return -EINVAL;
#if defined(CONFIG_X86_64)
*flags = disable_page_protection();
return 0;
#elif defined(CONFIG_ARM64)
return set_page_rw((unsigned long) (syscall_table + syscall_num));
#else
return -EOPNOTSUPP;
#endif
}
/** generic function to set a system call table to a read-only mode */
static inline long syscall_mode_ro(void **syscall_table, int syscall_num, unsigned long flags)
{
#if defined(CONFIG_X86_64)
reenable_page_protection(flags);
#elif defined(CONFIG_ARM64)
return set_page_ro((unsigned long) (syscall_table + syscall_num));
#else
return -EOPNOTSUPP;
#endif
return 0;
}
static inline int syscall_set_hook(void **syscall_table,
int syscall_num, void **orig_hook, void *new_hook)
{
int ret;
unsigned long flags;
ret = syscall_mode_rw(syscall_table, syscall_num, &flags);
if (ret) {
LOG_ERROR(ret, "failed to switch the system call table to the read-write mode");
return ret;
}
if (orig_hook)
*orig_hook = syscall_table[syscall_num];
syscall_table[syscall_num] = new_hook;
syscall_mode_ro(syscall_table, syscall_num, flags);
return 0;
}
static void restore_system_call_table(void)
{
if(system_call_table){
LOG_DEBUG("restoring system call table");
preempt_disable();
#ifdef USE_NEW_MOUNT_API
// kernel >= 6.6
syscall_set_hook(system_call_table, __NR_move_mount, NULL, orig_move_mount);
syscall_set_hook(system_call_table, __NR_mount_setattr, NULL, orig_mount_setattr);
syscall_set_hook(system_call_table, __NR_fsconfig, NULL, orig_fsconfig);
#else
// break back into the syscall table and replace the hooks we stole
syscall_set_hook(system_call_table, __NR_mount, NULL, orig_mount);
#endif // USE_NEW_MOUNT_API
syscall_set_hook(system_call_table, __NR_umount2, NULL, orig_umount);
#ifdef HAVE_SYS_OLDUMOUNT
syscall_set_hook(system_call_table, __NR_umount, NULL, orig_oldmount);
#endif
preempt_enable();
}
}
static int hook_system_call_table(void)
{
int ret = 0;
//find sys_call_table
LOG_DEBUG("locating system call table");
system_call_table = find_sys_call_table();
if(!system_call_table){
LOG_ERROR(-ENOENT, "failed to locate system call table, persistence disabled");
if (!SYS_CALL_TABLE_ADDR) {
LOG_WARN("make sure that CONFIG_KALLSYMS_ALL is enabled");
}
return -ENOENT;
}
preempt_disable();
#ifdef USE_NEW_MOUNT_API
// kernel >= 6.6
ret = syscall_set_hook(system_call_table, __NR_move_mount, (void **) &orig_move_mount, move_mount_hook);
ret |= syscall_set_hook(system_call_table, __NR_mount_setattr, (void **) &orig_mount_setattr, mount_setattr_hook);
ret |= syscall_set_hook(system_call_table, __NR_fsconfig, (void **) &orig_fsconfig, fsconfig_hook);
#else
//break into the syscall table and steal the hooks we need
ret = syscall_set_hook(system_call_table, __NR_mount, (void **) &orig_mount, mount_hook);
#endif // USE_NEW_MOUNT_API
ret |= syscall_set_hook(system_call_table, __NR_umount2, (void **) &orig_umount, umount_hook);
#ifdef HAVE_SYS_OLDUMOUNT
ret |= syscall_set_hook(system_call_table, __NR_umount, (void **) &orig_oldumount, oldumount_hook);
#endif
preempt_enable();
return ret;
}
#endif //!X64_SYS_CALL_ADDR
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2024 Synology Inc.
*/
// FTRACE_HOOKING
#if X64_SYS_CALL_ADDR
#include <linux/kprobes.h>
#ifdef USE_NEW_MOUNT_API
#include <linux/fs.h>
#include <linux/mount.h>
#include <linux/file.h>
#include <linux/fs_context.h>
#include <linux/fs_struct.h>
#include <linux/ns_common.h>
#endif
static inline bool dattobd_within_module(unsigned long addr, const struct module *mod)
{
#ifdef HAVE_WITHIN_MODULE
return within_module(addr, mod);
#else
return within_module_init(addr, mod) || within_module_core(addr, mod);
#endif
}
struct ftrace_hook {
const char *name;
void *function;
void *original;
unsigned long address;
struct ftrace_ops ops;
};
#define HOOK(_name, _function, _original) \
{ \
.name = (_name), \
.function = (_function), \
.original = (_original), \
}
#define USE_FENTRY_OFFSET 0
static struct ftrace_hook ftrace_hooks[] = {
#ifdef USE_NEW_MOUNT_API
HOOK("__x64_sys_mount_setattr", mount_setattr_hook, &orig_mount_setattr),
HOOK("__x64_sys_move_mount", move_mount_hook, &orig_move_mount),
HOOK("__x64_sys_fsconfig", fsconfig_hook, &orig_fsconfig),
#else
HOOK("__x64_sys_mount", mount_hook, &orig_mount),
#endif
HOOK("__x64_sys_umount", umount_hook, &orig_umount),
};
// Needs CONFIG_KPROBES=y as well as CONFIG_KALLSYMS=y
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
static unsigned long lookup_name(const char *name)
{
struct kprobe kp = {
.symbol_name = name
};
unsigned long address = 0;
int ret = 0;
ret = register_kprobe(&kp);
if (ret < 0)
{
LOG_ERROR(ret, "failed registering kprobe for %s", name);
return 0;
}
address = (unsigned long)kp.addr;
LOG_DEBUG("using kprobe to find address of name : %s: %lx", name, address);
unregister_kprobe(&kp);
return address;
}
#else
static unsigned long lookup_name(const char *name)
{
unsigned long address;
address = kallsyms_lookup_name(name);
LOG_DEBUG("kallsyms_lookup_name address of name : %s: %lx", name, address);
return address;
}
#endif //LINUX_VERSION_CODE
static int resolve_hook_address(struct ftrace_hook *hook)
{
hook->address = lookup_name(hook->name);
if (!hook->address) {
LOG_ERROR(-ENOENT, "unresolved symbol: %s", hook->name);
return -ENOENT;
}
#if USE_FENTRY_OFFSET
*((unsigned long*) hook->original) = hook->address + MCOUNT_INSN_SIZE;
#else
*((unsigned long*) hook->original) = hook->address;
#endif //USE_FENTRY_OFFSET
return 0;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(5,11,0)
#define FTRACE_OPS_FL_RECURSION FTRACE_OPS_FL_RECURSION_SAFE
#define ftrace_regs pt_regs
static __always_inline struct pt_regs *ftrace_get_regs(struct ftrace_regs *fregs)
{
return fregs;
}
#endif
static void notrace ftrace_callback_handler(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *ops, struct ftrace_regs *fregs)
{
struct pt_regs *regs = ftrace_get_regs(fregs);
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);
#if USE_FENTRY_OFFSET
regs->ip = (unsigned long)hook->function;
#else
if (!dattobd_within_module(parent_ip, THIS_MODULE)){
regs->ip = (unsigned long)hook->function;
}
#endif //USE_FENTRY_OFFSET
}
/**
* register_hook() - registers and enables a single hook
* @hook: a hook to install
*
* Return:
* 0 - success
* !0 - an errno indicating the error
*/
static int register_hook(struct ftrace_hook *hook)
{
int ret = 0;
ret = resolve_hook_address(hook);
if (ret)
{
LOG_ERROR(ret, "failed resolving hook address for %s", hook->name);
return ret;
}
hook->ops.func = ftrace_callback_handler;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS | FTRACE_OPS_FL_RECURSION | FTRACE_OPS_FL_IPMODIFY;
ret = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
if (ret) {
LOG_ERROR(ret, "failed setting ftrace filter ip: %d for %s", ret, hook->name);
return ret;
}
ret = register_ftrace_function(&hook->ops);
if (ret) {
LOG_ERROR(ret, "failed registering ftrace function for %s", hook->name);
ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
return ret;
}
LOG_DEBUG("registered ftrace hook for %s", hook->name);
return ret;
}
/**
* unregister_hook() - disable and unregister a single hook
* @hook: a hook to remove
*
* Return:
* 0 - success
* !0 - an errno indicating the error
*/
static int unregister_hook(struct ftrace_hook *hook)
{
int ret = 0;
ret = unregister_ftrace_function(&hook->ops);
if (ret) {
LOG_ERROR(ret, "failed unregistering ftrace function for %s", hook->name);
}
ret = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
if (ret) {
LOG_ERROR(ret, "failed setting ftrace filter ip for %s", hook->name);
}
return ret;
}
static int register_ftrace_hooks(void)
{
int ret = 0;
int i;
int count = ARRAY_SIZE(ftrace_hooks);
for (i = 0; i < count; i++) {
ret = register_hook(&ftrace_hooks[i]);
if (ret)
goto error;
}
return 0;
error:
while (i != 0) {
unregister_hook(&ftrace_hooks[--i]);
}
return ret;
}
static int unregister_ftrace_hooks(void)
{
int ret = 0;
int i;
int count = ARRAY_SIZE(ftrace_hooks);
for (i = 0; i < count; i++) {
unregister_hook(&ftrace_hooks[i]);
}
return ret;
}
#endif //X64_SYS_CALL_ADDR
/***************************BLOCK DEVICE DRIVER***************************/
static int __tracer_add_ref(struct snap_device *dev, int ref_cnt){
int ret = 0;
if(!dev){
ret = -EFAULT;
LOG_ERROR(ret, "requested snapshot device does not exist");
goto error;
}
atomic_add(ref_cnt, &dev->sd_refs);
error:
return ret;
}
#define __tracer_open(dev) __tracer_add_ref(dev, 1)
#define __tracer_close(dev) __tracer_add_ref(dev, -1)
#ifdef HAVE_BDOPS_OPEN_INODE
//#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
static int snap_open(struct inode *inode, struct file *filp){
return __tracer_open(inode->i_bdev->bd_disk->private_data);
}
static int snap_release(struct inode *inode, struct file *filp){
return __tracer_close(inode->i_bdev->bd_disk->private_data);
}
#elif defined HAVE_BDOPS_OPEN_INT
//#elif LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)
static int snap_open(struct block_device *bdev, fmode_t mode){
return __tracer_open(bdev->bd_disk->private_data);
}
static int snap_release(struct gendisk *gd, fmode_t mode){
return __tracer_close(gd->private_data);
}
#elif defined HAVE_BDOPS_OPEN_GENDISK
static int snap_open(struct gendisk *gd, fmode_t mode){
return __tracer_open(gd->private_data);
}
static void snap_release(struct gendisk *gd){
__tracer_close(gd->private_data);
}
#else
static int snap_open(struct block_device *bdev, fmode_t mode){
return __tracer_open(bdev->bd_disk->private_data);
}
static void snap_release(struct gendisk *gd, fmode_t mode){
__tracer_close(gd->private_data);
}
#endif
static int elastio_snap_proc_show(struct seq_file *m, void *v){
int error, i;
struct snap_device **dev_ptr = v;
struct snap_device *dev = NULL;
//print the header if the "pointer" really an indication to do so
if(dev_ptr == SEQ_START_TOKEN){
seq_printf(m, "{\n");
seq_printf(m, "\t\"version\": \"%s\",\n", ELASTIO_SNAP_VERSION);
seq_printf(m, "\t\"devices\": [\n");
}
//if the pointer is actually a device print it
if(dev_ptr != SEQ_START_TOKEN && *dev_ptr != NULL){
dev = *dev_ptr;
if(dev->sd_minor != lowest_minor) seq_printf(m, ",\n");
seq_printf(m, "\t\t{\n");
seq_printf(m, "\t\t\t\"minor\": %u,\n", dev->sd_minor);
seq_printf(m, "\t\t\t\"cow_file\": \"%s\",\n", dev->sd_cow_path);
seq_printf(m, "\t\t\t\"block_device\": \"%s\",\n", dev->sd_bdev_path);
seq_printf(m, "\t\t\t\"max_cache\": %lu,\n", (dev->sd_cache_size)? dev->sd_cache_size : elastio_snap_cow_max_memory_default);
if(!test_bit(UNVERIFIED, &dev->sd_state)){
seq_printf(m, "\t\t\t\"fallocate\": %llu,\n", dev->sd_cow->file_max);
if(dev->sd_cow){
seq_printf(m, "\t\t\t\"seq_id\": %llu,\n", (unsigned long long)dev->sd_cow->seqid);
seq_printf(m, "\t\t\t\"uuid\": \"");
for(i = 0; i < COW_UUID_SIZE; i++){
seq_printf(m, "%02x", dev->sd_cow->uuid[i]);
}
seq_printf(m, "\",\n");
if(dev->sd_cow->version > COW_VERSION_0){
seq_printf(m, "\t\t\t\"version\": %llu,\n", dev->sd_cow->version);
seq_printf(m, "\t\t\t\"nr_changed_blocks\": %llu,\n", dev->sd_cow->nr_changed_blocks);
seq_printf(m, "\t\t\t\"cancellation mode\": %d,\n", snap_is_cancelled(dev->sd_cow));
}
}
}
error = tracer_read_fail_state(dev);
if(error) seq_printf(m, "\t\t\t\"error\": %d,\n", error);
seq_printf(m, "\t\t\t\"state\": %lu,\n", dev->sd_state);
seq_printf(m, "\t\t\t\"ignore_errors\": %i,\n", dev->sd_ignore_snap_errors);
seq_printf(m, "\t\t\t\"bio_op_discard_ignored\": %llu,\n", atomic64_read(&dev->sd_discard_dropped_cnt));
seq_printf(m, "\t\t\t\"discard_sectors_ignored\": %llu,\n", atomic64_read(&dev->sd_discard_ignored_size));
seq_printf(m, "\t\t\t\"cow_on_bdev\": %s\n", test_bit(COW_ON_BDEV, &dev->sd_cow_state) ? "true" : "false");
seq_printf(m, "\t\t}");
}
//print the footer if there are no devices to print or if this device has the highest minor
if((dev_ptr == SEQ_START_TOKEN && lowest_minor > highest_minor) || (dev && dev->sd_minor == highest_minor)){
seq_printf(m, "\n\t]\n");
seq_printf(m, "}\n");
}
return 0;
}
static inline void *elastio_snap_proc_get_idx(loff_t pos){
if(pos > highest_minor) return NULL;
return &snap_devices[pos];
}
static void *elastio_snap_proc_start(struct seq_file *m, loff_t *pos){
if(*pos == 0) return SEQ_START_TOKEN;
return elastio_snap_proc_get_idx(*pos - 1);
}
static void *elastio_snap_proc_next(struct seq_file *m, void *v, loff_t *pos){
void *dev = elastio_snap_proc_get_idx(*pos);
++*pos;
return dev;
}
static void elastio_snap_proc_stop(struct seq_file *m, void *v){
}
static int elastio_snap_proc_open(struct inode *inode, struct file *filp){
mutex_lock(&ioctl_mutex);
return seq_open(filp, &elastio_snap_seq_proc_ops);
}
static int elastio_snap_proc_release(struct inode *inode, struct file *file){
seq_release(inode, file);
mutex_unlock(&ioctl_mutex);
return 0;
}
static void elastio_snap_wait_for_release(struct snap_device *dev)
{
#ifdef HAVE_TASK_STRUCT_STATE
// Linux kernel version 5.14+
int prev_state = READ_ONCE(current->__state);
#else
int prev_state = ACCESS_ONCE(current->state);
#endif
int i = 0;
set_current_state(TASK_INTERRUPTIBLE);
while (atomic_read(&dev->sd_refs) && i < ELASTIO_SNAP_WAIT_FOR_RELEASE_MAX_SLEEP_COUNT) {
msleep(ELASTIO_SNAP_WAIT_FOR_RELEASE_MSEC / ELASTIO_SNAP_WAIT_FOR_RELEASE_MAX_SLEEP_COUNT);
++i;
}
set_current_state(prev_state);
}
/************************MODULE SETUP AND DESTROY************************/
static void agent_exit(void){
int i;
struct snap_device *dev;
LOG_DEBUG("module exit");
#if X64_SYS_CALL_ADDR
LOG_DEBUG("unregistering ftrace hooks");
unregister_ftrace_hooks();
#else
LOG_DEBUG("restoring system call tables");
restore_system_call_table();
#endif
//unregister control device
LOG_DEBUG("unregistering control device");
misc_deregister(&snap_control_device);
//unregister proc info file
LOG_DEBUG("unregistering /proc file");
remove_proc_entry(INFO_PROC_FILE, NULL);
//destroy our snap devices
LOG_DEBUG("destroying snap devices");
if(snap_devices){
tracer_for_each(dev, i){
if(dev){
LOG_DEBUG("destroying minor - %d", i);
tracer_destroy(dev);
}
}
kfree(snap_devices);
snap_devices = NULL;
}
//unregister our block device driver
LOG_DEBUG("unregistering device driver from the kernel");
unregister_blkdev(major, DRIVER_NAME);
#ifdef NETLINK_DEBUG
nl_trace_event_generic(NL_EVENT_DRIVER_DEINIT, 0);
nl_debug_release();
#endif
}
module_exit(agent_exit);
static int __init agent_init(void){
int ret;
LOG_DEBUG("module init");
#ifdef NETLINK_DEBUG
ret = nl_debug_init();
if (ret) {
LOG_DEBUG("failing driver init");
return ret;
}
nl_trace_event_generic(NL_EVENT_DRIVER_INIT, 0);
#endif
//init ioctl mutex
mutex_init(&ioctl_mutex);
//init minor range
if(elastio_snap_max_snap_devices == 0 || elastio_snap_max_snap_devices > ELASTIO_SNAP_MAX_SNAP_DEVICES){
const unsigned int nr_devices = elastio_snap_max_snap_devices == 0 ? ELASTIO_SNAP_DEFAULT_SNAP_DEVICES : ELASTIO_SNAP_MAX_SNAP_DEVICES;
LOG_WARN("invalid number of snapshot devices (%u), setting to %u", elastio_snap_max_snap_devices, nr_devices);
elastio_snap_max_snap_devices = nr_devices;
}
highest_minor = 0;
lowest_minor = elastio_snap_max_snap_devices - 1;
//get a major number for the driver
LOG_DEBUG("get major number");
major = register_blkdev(0, DRIVER_NAME);
if(major <= 0){
ret = -EBUSY;
LOG_ERROR(ret, "error requesting major number from the kernel");
goto error;
}
//allocate global device array
LOG_DEBUG("allocate global device array");
snap_devices = kzalloc(elastio_snap_max_snap_devices * sizeof(struct snap_device*), GFP_KERNEL);
if(!snap_devices){
ret = -ENOMEM;
LOG_ERROR(ret, "error allocating global device array");
goto error;
}
//register proc file
LOG_DEBUG("registering proc file");
info_proc = proc_create(INFO_PROC_FILE, 0, NULL, &elastio_snap_proc_fops);
if(!info_proc){
ret = -ENOENT;
LOG_ERROR(ret, "error registering proc file");
goto error;
}
//register control device
LOG_DEBUG("registering control device");
ret = misc_register(&snap_control_device);
if(ret){
LOG_ERROR(ret, "error registering control device");
goto error;
}
if (elastio_snap_may_hook_syscalls) {
#if X64_SYS_CALL_ADDR
LOG_DEBUG("using ftrace hooks insteam of system call table");
ret = register_ftrace_hooks();
if (ret) {
LOG_ERROR(ret, "ftrace hooks failed");
goto error;
}
#else
ret = hook_system_call_table();
if (ret) {
LOG_ERROR(ret, "couldn't hook the syscall table");
}
#endif
}
return 0;
error:
agent_exit();
return ret;
}
module_init(agent_init);