// SPDX-License-Identifier: GPL-2.0+ #include #include #include #include #include #include #include #include #include #include #include #include #include LIST_HEAD(simple_proc_file_list); struct simple_proc_file_internal { struct list_head list; DECLARE_PROC_COMPAT_FOPS(fops); char *path; int (*kernel_input)(char *send, void *private); void (*kernel_output)(struct seq_file *pseq, void *private); void *priv_data; size_t expected_output_buffer_size; }; static int simple_proc_file_show(struct seq_file *m, void *offset) { struct simple_proc_file_internal *spf = m->private; if (spf->kernel_output) { sp_debug_print("offset: %#p\n", offset); spf->kernel_output(m, spf->priv_data); } return 0; } static int simple_proc_file_open(struct inode *inode, struct file *file) { unsigned int res; struct simple_proc_file_internal *spf = PDE_DATA(inode); sp_debug_print( "call open with simple_proc_file_entry %p: %s, priv=%p\n", spf, spf->path, spf->priv_data); if (spf->expected_output_buffer_size) { res = single_open_size(file, simple_proc_file_show, spf, spf->expected_output_buffer_size); } else { res = single_open(file, simple_proc_file_show, spf); } return res; } /** */ static ssize_t simple_proc_file_write(struct file *file, const char *buffer, size_t count, loff_t *offset) { unsigned char *procfs_buffer; ssize_t res; size_t procfs_buffer_size; struct simple_proc_file_internal *spf; spf = PDE_DATA(file->f_inode); if (spf->kernel_input == NULL) { return -EFAULT; } /* * get buffer size: * we need one extra byte in our buffer: the terminating 0-byte * count variable contains user data len, this data is not 0-terminated */ if (count >= SP_MAX_INPUT_BUF) { procfs_buffer_size = SP_MAX_INPUT_BUF; } else { procfs_buffer_size = count + 1; } /* alloc buffer */ if (procfs_buffer_size > PAGE_SIZE) { procfs_buffer = vmalloc(procfs_buffer_size); } else { procfs_buffer = kmalloc(procfs_buffer_size, GFP_KERNEL); } if (!procfs_buffer) { return -ENOMEM; } /* write data to the buffer */ if (copy_from_user(procfs_buffer, buffer, procfs_buffer_size - 1)) { res = -EFAULT; goto fini; } procfs_buffer[procfs_buffer_size - 1] = 0; res = spf->kernel_input(procfs_buffer, spf->priv_data); if (res >= 0) { res = procfs_buffer_size - 1; } fini: if (is_vmalloc_addr(procfs_buffer)) { vfree(procfs_buffer); } else { kfree(procfs_buffer); } return res; } /** */ int add_simple_proc_file(const char *path, int (*kernel_input)(char *, void *), void (*kernel_output)(struct seq_file *, void *), void *priv_data) { struct simple_proc_file_internal *simple_proc_file_entry = NULL; unsigned int path_len; /* path check */ path_len = strlen(path) + 1; if (path_len > SP_MAX_PATH_LEN) { return -EINVAL; } /* alloc private data structure */ simple_proc_file_entry = kzalloc(sizeof(*simple_proc_file_entry) + path_len, GFP_KERNEL); if (!simple_proc_file_entry) { return -ENOMEM; } simple_proc_file_entry->path = (char *)(simple_proc_file_entry + 1); if (kernel_input) { PROC_SET_COMPAT_FOPS(simple_proc_file_entry->fops, simple_proc_file_open, seq_read, simple_proc_file_write, seq_lseek, single_release); } else { PROC_SET_COMPAT_FOPS(simple_proc_file_entry->fops, simple_proc_file_open, seq_read, NULL, seq_lseek, single_release); } /*setup more */ simple_proc_file_entry->priv_data = priv_data; simple_proc_file_entry->kernel_input = kernel_input; simple_proc_file_entry->kernel_output = kernel_output; /* copy path */ memcpy(simple_proc_file_entry->path, path, path_len); /* create proc file */ if (!proc_create_data(path, 0, NULL, &simple_proc_file_entry->fops, simple_proc_file_entry)) { kfree(simple_proc_file_entry); pr_err("%s: proc_create_data failed\n", __func__); return -EFAULT; } list_add(&simple_proc_file_entry->list, &simple_proc_file_list); sp_debug_print("register simple_proc_file_entry %p\n", simple_proc_file_entry); return 0; } EXPORT_SYMBOL(add_simple_proc_file); /** */ void remove_simple_proc_file(const char *path) { struct simple_proc_file_internal *spf; list_for_each_entry(spf, &simple_proc_file_list, list) { if (strncmp(spf->path, path, SP_MAX_PATH_LEN) == 0) { list_del(&spf->list); // kfree(spf) is done in remove_proc_entry remove_proc_entry(path, NULL); sp_debug_print("remove entry %s\n", path); return; } } } EXPORT_SYMBOL(remove_simple_proc_file); /** */ int add_simple_proc_file_array(struct simple_proc_file *array) { int i; int res = 0; for (i = 0; array[i].path != NULL; i++) { if (array[i].enabled) { int tres = add_simple_proc_file(array[i].path, array[i].kernel_input, array[i].kernel_output, array[i].priv_data); if (tres) res = tres; } } return res; } EXPORT_SYMBOL(add_simple_proc_file_array); /** */ void remove_simple_proc_file_array(struct simple_proc_file *array) { int i; for (i = 0; array[i].path != NULL; i++) { if (array[i].enabled) remove_simple_proc_file(array[i].path); } } EXPORT_SYMBOL(remove_simple_proc_file_array); /** */ void simple_proc_file_setup_expected_output_size(const char *path, size_t expected_output_size) { struct simple_proc_file_internal *spf; list_for_each_entry (spf, &simple_proc_file_list, list) { if (strncmp(spf->path, path, SP_MAX_PATH_LEN) == 0) { spf->expected_output_buffer_size = expected_output_size; } } } EXPORT_SYMBOL(simple_proc_file_setup_expected_output_size); MODULE_LICENSE("GPL");