| /* |
| * What: /sys/kernel/debug/orangefs/debug-help |
| * Date: June 2015 |
| * Contact: Mike Marshall <hubcap@omnibond.com> |
| * Description: |
| * List of client and kernel debug keywords. |
| * |
| * |
| * What: /sys/kernel/debug/orangefs/client-debug |
| * Date: June 2015 |
| * Contact: Mike Marshall <hubcap@omnibond.com> |
| * Description: |
| * Debug setting for "the client", the userspace |
| * helper for the kernel module. |
| * |
| * |
| * What: /sys/kernel/debug/orangefs/kernel-debug |
| * Date: June 2015 |
| * Contact: Mike Marshall <hubcap@omnibond.com> |
| * Description: |
| * Debug setting for the orangefs kernel module. |
| * |
| * Any of the keywords, or comma-separated lists |
| * of keywords, from debug-help can be catted to |
| * client-debug or kernel-debug. |
| * |
| * "none", "all" and "verbose" are special keywords |
| * for client-debug. Setting client-debug to "all" |
| * is kind of like trying to drink water from a |
| * fire hose, "verbose" triggers most of the same |
| * output except for the constant flow of output |
| * from the main wait loop. |
| * |
| * "none" and "all" are similar settings for kernel-debug |
| * no need for a "verbose". |
| */ |
| #include <linux/debugfs.h> |
| #include <linux/slab.h> |
| |
| #include <linux/uaccess.h> |
| |
| #include "pvfs2-debugfs.h" |
| #include "protocol.h" |
| #include "pvfs2-kernel.h" |
| |
| static int orangefs_debug_disabled = 1; |
| |
| static int orangefs_debug_help_open(struct inode *, struct file *); |
| |
| const struct file_operations debug_help_fops = { |
| .open = orangefs_debug_help_open, |
| .read = seq_read, |
| .release = seq_release, |
| .llseek = seq_lseek, |
| }; |
| |
| static void *help_start(struct seq_file *, loff_t *); |
| static void *help_next(struct seq_file *, void *, loff_t *); |
| static void help_stop(struct seq_file *, void *); |
| static int help_show(struct seq_file *, void *); |
| |
| static const struct seq_operations help_debug_ops = { |
| .start = help_start, |
| .next = help_next, |
| .stop = help_stop, |
| .show = help_show, |
| }; |
| |
| /* |
| * Used to protect data in ORANGEFS_KMOD_DEBUG_FILE and |
| * ORANGEFS_KMOD_DEBUG_FILE. |
| */ |
| static DEFINE_MUTEX(orangefs_debug_lock); |
| |
| int orangefs_debug_open(struct inode *, struct file *); |
| |
| static ssize_t orangefs_debug_read(struct file *, |
| char __user *, |
| size_t, |
| loff_t *); |
| |
| static ssize_t orangefs_debug_write(struct file *, |
| const char __user *, |
| size_t, |
| loff_t *); |
| |
| static const struct file_operations kernel_debug_fops = { |
| .open = orangefs_debug_open, |
| .read = orangefs_debug_read, |
| .write = orangefs_debug_write, |
| .llseek = generic_file_llseek, |
| }; |
| |
| /* |
| * initialize kmod debug operations, create orangefs debugfs dir and |
| * ORANGEFS_KMOD_DEBUG_HELP_FILE. |
| */ |
| int pvfs2_debugfs_init(void) |
| { |
| |
| int rc = -ENOMEM; |
| |
| debug_dir = debugfs_create_dir("orangefs", NULL); |
| if (!debug_dir) |
| goto out; |
| |
| help_file_dentry = debugfs_create_file(ORANGEFS_KMOD_DEBUG_HELP_FILE, |
| 0444, |
| debug_dir, |
| debug_help_string, |
| &debug_help_fops); |
| if (!help_file_dentry) |
| goto out; |
| |
| orangefs_debug_disabled = 0; |
| rc = 0; |
| |
| out: |
| if (rc) |
| pvfs2_debugfs_cleanup(); |
| |
| return rc; |
| } |
| |
| void pvfs2_debugfs_cleanup(void) |
| { |
| debugfs_remove_recursive(debug_dir); |
| } |
| |
| /* open ORANGEFS_KMOD_DEBUG_HELP_FILE */ |
| static int orangefs_debug_help_open(struct inode *inode, struct file *file) |
| { |
| int rc = -ENODEV; |
| int ret; |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "orangefs_debug_help_open: start\n"); |
| |
| if (orangefs_debug_disabled) |
| goto out; |
| |
| ret = seq_open(file, &help_debug_ops); |
| if (ret) |
| goto out; |
| |
| ((struct seq_file *)(file->private_data))->private = inode->i_private; |
| |
| rc = 0; |
| |
| out: |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "orangefs_debug_help_open: rc:%d:\n", |
| rc); |
| return rc; |
| } |
| |
| /* |
| * I think start always gets called again after stop. Start |
| * needs to return NULL when it is done. The whole "payload" |
| * in this case is a single (long) string, so by the second |
| * time we get to start (pos = 1), we're done. |
| */ |
| static void *help_start(struct seq_file *m, loff_t *pos) |
| { |
| void *payload = NULL; |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "help_start: start\n"); |
| |
| if (*pos == 0) |
| payload = m->private; |
| |
| return payload; |
| } |
| |
| static void *help_next(struct seq_file *m, void *v, loff_t *pos) |
| { |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "help_next: start\n"); |
| |
| return NULL; |
| } |
| |
| static void help_stop(struct seq_file *m, void *p) |
| { |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "help_stop: start\n"); |
| } |
| |
| static int help_show(struct seq_file *m, void *v) |
| { |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "help_show: start\n"); |
| |
| seq_puts(m, v); |
| |
| return 0; |
| } |
| |
| /* |
| * initialize the kernel-debug file. |
| */ |
| int pvfs2_kernel_debug_init(void) |
| { |
| |
| int rc = -ENOMEM; |
| struct dentry *ret; |
| char *k_buffer = NULL; |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "%s: start\n", __func__); |
| |
| k_buffer = kzalloc(PVFS2_MAX_DEBUG_STRING_LEN, GFP_KERNEL); |
| if (!k_buffer) |
| goto out; |
| |
| if (strlen(kernel_debug_string) + 1 < PVFS2_MAX_DEBUG_STRING_LEN) { |
| strcpy(k_buffer, kernel_debug_string); |
| strcat(k_buffer, "\n"); |
| } else { |
| strcpy(k_buffer, "none\n"); |
| pr_info("%s: overflow 1!\n", __func__); |
| } |
| |
| ret = debugfs_create_file(ORANGEFS_KMOD_DEBUG_FILE, |
| 0444, |
| debug_dir, |
| k_buffer, |
| &kernel_debug_fops); |
| if (!ret) { |
| pr_info("%s: failed to create %s.\n", |
| __func__, |
| ORANGEFS_KMOD_DEBUG_FILE); |
| goto out; |
| } |
| |
| rc = 0; |
| |
| out: |
| if (rc) |
| pvfs2_debugfs_cleanup(); |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "%s: rc:%d:\n", __func__, rc); |
| return rc; |
| } |
| |
| /* |
| * initialize the client-debug file. |
| */ |
| int pvfs2_client_debug_init(void) |
| { |
| |
| int rc = -ENOMEM; |
| char *c_buffer = NULL; |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "%s: start\n", __func__); |
| |
| c_buffer = kzalloc(PVFS2_MAX_DEBUG_STRING_LEN, GFP_KERNEL); |
| if (!c_buffer) |
| goto out; |
| |
| if (strlen(client_debug_string) + 1 < PVFS2_MAX_DEBUG_STRING_LEN) { |
| strcpy(c_buffer, client_debug_string); |
| strcat(c_buffer, "\n"); |
| } else { |
| strcpy(c_buffer, "none\n"); |
| pr_info("%s: overflow! 2\n", __func__); |
| } |
| |
| client_debug_dentry = debugfs_create_file(ORANGEFS_CLIENT_DEBUG_FILE, |
| 0444, |
| debug_dir, |
| c_buffer, |
| &kernel_debug_fops); |
| if (!client_debug_dentry) { |
| pr_info("%s: failed to create %s.\n", |
| __func__, |
| ORANGEFS_CLIENT_DEBUG_FILE); |
| goto out; |
| } |
| |
| rc = 0; |
| |
| out: |
| if (rc) |
| pvfs2_debugfs_cleanup(); |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "%s: rc:%d:\n", __func__, rc); |
| return rc; |
| } |
| |
| /* open ORANGEFS_KMOD_DEBUG_FILE or ORANGEFS_CLIENT_DEBUG_FILE.*/ |
| int orangefs_debug_open(struct inode *inode, struct file *file) |
| { |
| int rc = -ENODEV; |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "%s: orangefs_debug_disabled: %d\n", |
| __func__, |
| orangefs_debug_disabled); |
| |
| if (orangefs_debug_disabled) |
| goto out; |
| |
| rc = 0; |
| mutex_lock(&orangefs_debug_lock); |
| file->private_data = inode->i_private; |
| mutex_unlock(&orangefs_debug_lock); |
| |
| out: |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "orangefs_debug_open: rc: %d\n", |
| rc); |
| return rc; |
| } |
| |
| static ssize_t orangefs_debug_read(struct file *file, |
| char __user *ubuf, |
| size_t count, |
| loff_t *ppos) |
| { |
| char *buf; |
| int sprintf_ret; |
| ssize_t read_ret = -ENOMEM; |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, "orangefs_debug_read: start\n"); |
| |
| buf = kmalloc(PVFS2_MAX_DEBUG_STRING_LEN, GFP_KERNEL); |
| if (!buf) |
| goto out; |
| |
| mutex_lock(&orangefs_debug_lock); |
| sprintf_ret = sprintf(buf, "%s", (char *)file->private_data); |
| mutex_unlock(&orangefs_debug_lock); |
| |
| read_ret = simple_read_from_buffer(ubuf, count, ppos, buf, sprintf_ret); |
| |
| kfree(buf); |
| |
| out: |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "orangefs_debug_read: ret: %zu\n", |
| read_ret); |
| |
| return read_ret; |
| } |
| |
| static ssize_t orangefs_debug_write(struct file *file, |
| const char __user *ubuf, |
| size_t count, |
| loff_t *ppos) |
| { |
| char *buf; |
| int rc = -EFAULT; |
| size_t silly = 0; |
| char *debug_string; |
| struct pvfs2_kernel_op_s *new_op = NULL; |
| struct client_debug_mask c_mask = { NULL, 0, 0 }; |
| |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "orangefs_debug_write: %s\n", |
| file->f_path.dentry->d_name.name); |
| |
| /* |
| * Thwart users who try to jamb a ridiculous number |
| * of bytes into the debug file... |
| */ |
| if (count > PVFS2_MAX_DEBUG_STRING_LEN + 1) { |
| silly = count; |
| count = PVFS2_MAX_DEBUG_STRING_LEN + 1; |
| } |
| |
| buf = kmalloc(PVFS2_MAX_DEBUG_STRING_LEN, GFP_KERNEL); |
| if (!buf) |
| goto out; |
| memset(buf, 0, PVFS2_MAX_DEBUG_STRING_LEN); |
| |
| if (copy_from_user(buf, ubuf, count - 1)) { |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "%s: copy_from_user failed!\n", |
| __func__); |
| goto out; |
| } |
| |
| /* |
| * Map the keyword string from userspace into a valid debug mask. |
| * The mapping process involves mapping the human-inputted string |
| * into a valid mask, and then rebuilding the string from the |
| * verified valid mask. |
| * |
| * A service operation is required to set a new client-side |
| * debug mask. |
| */ |
| if (!strcmp(file->f_path.dentry->d_name.name, |
| ORANGEFS_KMOD_DEBUG_FILE)) { |
| debug_string_to_mask(buf, &gossip_debug_mask, 0); |
| debug_mask_to_string(&gossip_debug_mask, 0); |
| debug_string = kernel_debug_string; |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "New kernel debug string is %s\n", |
| kernel_debug_string); |
| } else { |
| /* Can't reset client debug mask if client is not running. */ |
| if (is_daemon_in_service()) { |
| pr_info("%s: Client not running :%d:\n", |
| __func__, |
| is_daemon_in_service()); |
| goto out; |
| } |
| |
| debug_string_to_mask(buf, &c_mask, 1); |
| debug_mask_to_string(&c_mask, 1); |
| debug_string = client_debug_string; |
| |
| new_op = op_alloc(PVFS2_VFS_OP_PARAM); |
| if (!new_op) { |
| pr_info("%s: op_alloc failed!\n", __func__); |
| goto out; |
| } |
| |
| new_op->upcall.req.param.op = |
| PVFS2_PARAM_REQUEST_OP_TWO_MASK_VALUES; |
| new_op->upcall.req.param.type = PVFS2_PARAM_REQUEST_SET; |
| memset(new_op->upcall.req.param.s_value, |
| 0, |
| PVFS2_MAX_DEBUG_STRING_LEN); |
| sprintf(new_op->upcall.req.param.s_value, |
| "%llx %llx\n", |
| c_mask.mask1, |
| c_mask.mask2); |
| |
| /* service_operation returns 0 on success... */ |
| rc = service_operation(new_op, |
| "pvfs2_param", |
| PVFS2_OP_INTERRUPTIBLE); |
| |
| if (rc) |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "%s: service_operation failed! rc:%d:\n", |
| __func__, |
| rc); |
| |
| op_release(new_op); |
| } |
| |
| mutex_lock(&orangefs_debug_lock); |
| memset(file->f_inode->i_private, 0, PVFS2_MAX_DEBUG_STRING_LEN); |
| sprintf((char *)file->f_inode->i_private, "%s\n", debug_string); |
| mutex_unlock(&orangefs_debug_lock); |
| |
| *ppos += count; |
| if (silly) |
| rc = silly; |
| else |
| rc = count; |
| |
| out: |
| gossip_debug(GOSSIP_DEBUGFS_DEBUG, |
| "orangefs_debug_write: rc: %d\n", |
| rc); |
| kfree(buf); |
| return rc; |
| } |