%PDF- %PDF-
Direktori : /usr/src/blksnap-6.3.0.73/ |
Current File : //usr/src/blksnap-6.3.0.73/ctrl.c |
// SPDX-License-Identifier: GPL-2.0 #define pr_fmt(fmt) KBUILD_MODNAME "-ctrl: " fmt #include <linux/module.h> #include <linux/poll.h> #include <linux/uaccess.h> #include <linux/slab.h> #ifdef STANDALONE_BDEVFILTER #include "blk_snap.h" #else #include <linux/blk_snap.h> #endif #include "memory_checker.h" #include "ctrl.h" #include "params.h" #include "version.h" #include "snapshot.h" #include "snapimage.h" #include "tracker.h" #include "big_buffer.h" #ifdef STANDALONE_BDEVFILTER #include "log.h" #endif static int blk_snap_major; static long ctrl_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); static const struct file_operations ctrl_fops = { .owner = THIS_MODULE, .unlocked_ioctl = ctrl_unlocked_ioctl, }; static const struct blk_snap_version version = { .major = VERSION_MAJOR, .minor = VERSION_MINOR, .revision = VERSION_REVISION, .build = VERSION_BUILD, }; static const struct blk_snap_mod modification = { .name = MOD_NAME, .compatibility_flags = #ifdef BLK_SNAP_DEBUG_SECTOR_STATE (1ull << blk_snap_compat_flag_debug_sector_state) | #endif #ifdef BLK_SNAP_FILELOG (1ull << blk_snap_compat_flag_setlog) | #endif 0 }; int get_blk_snap_major(void) { return blk_snap_major; } int ctrl_init(void) { int ret; ret = register_chrdev(0, BLK_SNAP_MODULE_NAME, &ctrl_fops); if (ret < 0) { pr_err("Failed to register a character device. errno=%d\n", abs(blk_snap_major)); return ret; } blk_snap_major = ret; pr_info("Register control device [%d:0].\n", blk_snap_major); return 0; } void ctrl_done(void) { pr_info("Unregister control device\n"); unregister_chrdev(blk_snap_major, BLK_SNAP_MODULE_NAME); } static int ioctl_version(unsigned long arg) { if (copy_to_user((void *)arg, &version, sizeof(version))) { pr_err("Unable to get version: invalid user buffer\n"); return -ENODATA; } return 0; } static int ioctl_tracker_remove(unsigned long arg) { struct blk_snap_tracker_remove karg; if (copy_from_user(&karg, (void *)arg, sizeof(karg)) != 0) { pr_err("Unable to remove device from tracking: invalid user buffer\n"); return -ENODATA; } return tracker_remove(MKDEV(karg.dev_id.mj, karg.dev_id.mn)); } static int ioctl_tracker_collect(unsigned long arg) { int res; struct blk_snap_tracker_collect karg; struct blk_snap_cbt_info *cbt_info = NULL; pr_debug("Collecting tracking devices\n"); if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to collect tracking devices: invalid user buffer\n"); return -ENODATA; } if (!karg.cbt_info_array) { /* * If the buffer is empty, this is a request to determine * the number of trackers. */ res = tracker_collect(0, NULL, &karg.count); if (res) { pr_err("Failed to execute tracker_collect. errno=%d\n", abs(res)); return res; } if (copy_to_user((void *)arg, (void *)&karg, sizeof(karg))) { pr_err("Unable to collect tracking devices: invalid user buffer for arguments\n"); return -ENODATA; } return 0; } cbt_info = kcalloc(karg.count, sizeof(struct blk_snap_cbt_info), GFP_KERNEL); if (cbt_info == NULL) return -ENOMEM; memory_object_inc(memory_object_blk_snap_cbt_info); res = tracker_collect(karg.count, cbt_info, &karg.count); if (res) { pr_err("Failed to execute tracker_collect. errno=%d\n", abs(res)); goto fail; } if (copy_to_user(karg.cbt_info_array, cbt_info, karg.count * sizeof(struct blk_snap_cbt_info))) { pr_err("Unable to collect tracking devices: invalid user buffer for CBT info\n"); res = -ENODATA; goto fail; } if (copy_to_user((void *)arg, (void *)&karg, sizeof(karg))) { pr_err("Unable to collect tracking devices: invalid user buffer for arguments\n"); res = -ENODATA; goto fail; } fail: kfree(cbt_info); memory_object_dec(memory_object_blk_snap_cbt_info); return res; } static int ioctl_tracker_read_cbt_map(unsigned long arg) { struct blk_snap_tracker_read_cbt_bitmap karg; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to read CBT map: invalid user buffer\n"); return -ENODATA; } return tracker_read_cbt_bitmap(MKDEV(karg.dev_id.mj, karg.dev_id.mn), karg.offset, karg.length, (char __user *)karg.buff); } static int ioctl_tracker_mark_dirty_blocks(unsigned long arg) { int ret = 0; struct blk_snap_tracker_mark_dirty_blocks karg; struct blk_snap_block_range *dirty_blocks_array; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to mark dirty blocks: invalid user buffer\n"); return -ENODATA; } if (unlikely(karg.count > (KMALLOC_MAX_SIZE / sizeof(struct blk_snap_block_range)))) { pr_err("Unable to mark dirty blocks: the array of dirty blocks is too big\n"); return -ENOMEM; } dirty_blocks_array = kcalloc( karg.count, sizeof(struct blk_snap_block_range), GFP_KERNEL); if (!dirty_blocks_array) return -ENOMEM; memory_object_inc(memory_object_blk_snap_block_range); if (copy_from_user(dirty_blocks_array, (void *)karg.dirty_blocks_array, karg.count * sizeof(struct blk_snap_block_range))) { pr_err("Unable to mark dirty blocks: invalid user buffer\n"); ret = -ENODATA; } else { if (karg.dev_id.mj == snapimage_major()) ret = snapshot_mark_dirty_blocks( MKDEV(karg.dev_id.mj, karg.dev_id.mn), dirty_blocks_array, karg.count); else ret = tracker_mark_dirty_blocks( MKDEV(karg.dev_id.mj, karg.dev_id.mn), dirty_blocks_array, karg.count); } kfree(dirty_blocks_array); memory_object_dec(memory_object_blk_snap_block_range); return ret; } static int ioctl_snapshot_create(unsigned long arg) { int ret; struct blk_snap_snapshot_create karg; struct blk_snap_dev_t *dev_id_array = NULL; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to create snapshot: invalid user buffer\n"); return -ENODATA; } dev_id_array = kcalloc(karg.count, sizeof(struct blk_snap_dev_t), GFP_KERNEL); if (dev_id_array == NULL) { pr_err("Unable to create snapshot: too many devices %d\n", karg.count); return -ENOMEM; } memory_object_inc(memory_object_blk_snap_dev_t); if (copy_from_user(dev_id_array, (void *)karg.dev_id_array, karg.count * sizeof(struct blk_snap_dev_t))) { pr_err("Unable to create snapshot: invalid user buffer\n"); ret = -ENODATA; goto out; } ret = snapshot_create(dev_id_array, karg.count, &karg.id); if (ret) goto out; if (copy_to_user((void *)arg, &karg, sizeof(karg))) { pr_err("Unable to create snapshot: invalid user buffer\n"); ret = -ENODATA; } out: kfree(dev_id_array); memory_object_dec(memory_object_blk_snap_dev_t); return ret; } static int ioctl_snapshot_destroy(unsigned long arg) { struct blk_snap_snapshot_destroy karg; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to destroy snapshot: invalid user buffer\n"); return -ENODATA; } return snapshot_destroy(&karg.id); } static int ioctl_snapshot_append_storage(unsigned long arg) { int res = 0; struct blk_snap_snapshot_append_storage karg; struct big_buffer *ranges = NULL; size_t ranges_buffer_size; pr_debug("Append difference storage\n"); if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to append difference storage: invalid user buffer\n"); return -EINVAL; } /* * Rarely, but there are file systems in which the blocks on the disk * are significantly fragmented. And the drive for the diff storage can be * quite large. * At the same time, an attempt to allocate several pages of continuous * address space on such systems often causes an ENOMEM error. * Therefore, an array of pages is used to store an array of ranges of * available disk space. */ ranges_buffer_size = karg.count * sizeof(struct blk_snap_block_range); ranges = big_buffer_alloc(ranges_buffer_size, GFP_KERNEL); if (!ranges) { pr_err("Unable to append difference storage: cannot allocate [%zu] bytes\n", ranges_buffer_size); return -ENOMEM; } if (big_buffer_copy_from_user((void *)karg.ranges, 0, ranges, ranges_buffer_size) != ranges_buffer_size) { pr_err("Unable to add file to snapstore: invalid user buffer for parameters\n"); big_buffer_free(ranges); return -ENODATA; } res = snapshot_append_storage(&karg.id, karg.dev_id, ranges, (size_t)karg.count); big_buffer_free(ranges); return res; } static int ioctl_snapshot_take(unsigned long arg) { struct blk_snap_snapshot_take karg; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to take snapshot: invalid user buffer\n"); return -ENODATA; } return snapshot_take(&karg.id); } static int ioctl_snapshot_wait_event(unsigned long arg) { int ret = 0; struct blk_snap_snapshot_event *karg; struct event *event; karg = kzalloc(sizeof(struct blk_snap_snapshot_event), GFP_KERNEL); if (!karg) return -ENOMEM; memory_object_inc(memory_object_blk_snap_snapshot_event); if (copy_from_user(karg, (void *)arg, sizeof(struct blk_snap_snapshot_event))) { pr_err("Unable failed to get snapstore error code: invalid user buffer\n"); ret = -EINVAL; goto out; } event = snapshot_wait_event(&karg->id, karg->timeout_ms); if (IS_ERR(event)) { ret = PTR_ERR(event); goto out; } pr_debug("Received event=%lld code=%d data_size=%d\n", event->time, event->code, event->data_size); karg->code = event->code; karg->time_label = event->time; if (event->data_size > sizeof(karg->data)) { pr_err("Event size %d is too big\n", event->data_size); ret = -ENOSPC; /* If we can't copy all the data, we copy only part of it. */ } memcpy(karg->data, event->data, event->data_size); event_free(event); if (copy_to_user((void *)arg, karg, sizeof(struct blk_snap_snapshot_event))) { pr_err("Unable to get snapstore error code: invalid user buffer\n"); ret = -EINVAL; } out: kfree(karg); memory_object_dec(memory_object_blk_snap_snapshot_event); return ret; } static int ioctl_snapshot_collect(unsigned long arg) { int ret; struct blk_snap_snapshot_collect karg; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to collect available snapshots: invalid user buffer\n"); return -ENODATA; } ret = snapshot_collect(&karg.count, karg.ids); if (copy_to_user((void *)arg, &karg, sizeof(karg))) { pr_err("Unable to collect available snapshots: invalid user buffer\n"); return -ENODATA; } return ret; } static int ioctl_snapshot_collect_images(unsigned long arg) { int ret; struct blk_snap_snapshot_collect_images karg; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to collect snapshot images: invalid user buffer\n"); return -ENODATA; } ret = snapshot_collect_images(&karg.id, karg.image_info_array, &karg.count); if (copy_to_user((void *)arg, &karg, sizeof(karg))) { pr_err("Unable to collect snapshot images: invalid user buffer\n"); return -ENODATA; } return ret; } static int (*const blk_snap_ioctl_table[])(unsigned long arg) = { ioctl_version, ioctl_tracker_remove, ioctl_tracker_collect, ioctl_tracker_read_cbt_map, ioctl_tracker_mark_dirty_blocks, ioctl_snapshot_create, ioctl_snapshot_destroy, ioctl_snapshot_append_storage, ioctl_snapshot_take, ioctl_snapshot_collect, ioctl_snapshot_collect_images, ioctl_snapshot_wait_event, }; static_assert( sizeof(blk_snap_ioctl_table) == (blk_snap_ioctl_end * sizeof(void *)), "The size of table blk_snap_ioctl_table does not match the enum blk_snap_ioctl."); #ifdef BLK_SNAP_MODIFICATION static int ioctl_mod(unsigned long arg) { if (copy_to_user((void *)arg, &modification, sizeof(modification))) { pr_err("Unable to get modification: invalid user buffer\n"); return -ENODATA; } return 0; } static int ioctl_setlog(unsigned long arg) { #ifdef BLK_SNAP_FILELOG struct blk_snap_setlog karg; char *filepath = NULL; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to get log parameters: invalid user buffer\n"); return -ENODATA; } /* * logging can be disabled * To do this, it is enough not to specify a logging file or set * a negative logging level. */ if ((karg.level < 0) || !karg.filepath) return log_restart(-1, NULL, 0); if (karg.filepath_size == 0) { pr_err("Invalid parameters. 'filepath_size' cannot be zero\n"); return -EINVAL; } filepath = kzalloc(karg.filepath_size + 1, GFP_KERNEL); if (!filepath) return -ENOMEM; memory_object_inc(memory_object_log_filepath); if (copy_from_user(filepath, (void *)karg.filepath, karg.filepath_size)) { pr_err("Unable to get log filepath: invalid user buffer\n"); kfree(filepath); memory_object_dec(memory_object_log_filepath); return -ENODATA; } return log_restart(karg.level, filepath, karg.tz_minuteswest); #else return -ENOTTY; #endif } static int ioctl_get_sector_state(unsigned long arg) { #ifdef BLK_SNAP_DEBUG_SECTOR_STATE int ret; struct blk_snap_get_sector_state karg; dev_t dev_id; if (copy_from_user(&karg, (void *)arg, sizeof(karg))) { pr_err("Unable to get sector state: invalid user buffer\n"); return -ENODATA; } dev_id = MKDEV(karg.image_dev_id.mj, karg.image_dev_id.mn); ret = snapshot_get_chunk_state(dev_id, karg.sector, &karg.state); if (unlikely(ret)) { pr_err("Failed to get sector state: cannot get chunk state\n"); return ret; } if (copy_to_user((void *)arg, &karg, sizeof(karg))) { pr_err("Unable to get sector state: invalid user buffer\n"); return -ENODATA; } return ret; #else return -ENOTTY; #endif } static int (*const blk_snap_ioctl_table_mod[])(unsigned long arg) = { ioctl_mod, ioctl_setlog, ioctl_get_sector_state, }; static_assert( sizeof(blk_snap_ioctl_table_mod) == ((blk_snap_ioctl_end_mod - IOCTL_MOD) * sizeof(void *)), "The size of table blk_snap_ioctl_table_mod does not match the enum blk_snap_ioctl."); #endif static long ctrl_unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int nr = _IOC_NR(cmd); if (nr > (sizeof(blk_snap_ioctl_table) / sizeof(void *))) { #ifdef BLK_SNAP_MODIFICATION if ((nr >= IOCTL_MOD) && (nr < (IOCTL_MOD + (sizeof(blk_snap_ioctl_table_mod) / sizeof(void *))))) { nr -= IOCTL_MOD; if (blk_snap_ioctl_table_mod[nr]) return blk_snap_ioctl_table_mod[nr](arg); } #endif return -ENOTTY; } if (!blk_snap_ioctl_table[nr]) return -ENOTTY; return blk_snap_ioctl_table[nr](arg); }