| /* |
| * Event notification code. |
| * |
| * Copyright (C) 2005 FUJITA Tomonori <tomof@acm.org> |
| * Copyright (C) 2007 - 2018 Vladislav Bolkhovitin |
| * Copyright (C) 2007 - 2018 Western Digital Corporation |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| |
| #include <linux/module.h> |
| #ifdef INSIDE_KERNEL_TREE |
| #include <scst/iscsi_scst.h> |
| #else |
| #include "iscsi_scst.h" |
| #endif |
| #include "iscsi_trace_flag.h" |
| #include "iscsi.h" |
| |
| struct net *iscsi_net_ns; |
| EXPORT_SYMBOL(iscsi_net_ns); |
| |
| /* See also commit 2d4bc93368f5 ("netlink: extended ACK reporting") */ |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0) |
| struct netlink_ext_ack; |
| |
| static inline void netlink_ack_backport(struct sk_buff *in_skb, |
| struct nlmsghdr *nlh, int err, |
| const struct netlink_ext_ack *extack) |
| { |
| WARN_ON_ONCE(extack); |
| netlink_ack(in_skb, nlh, err); |
| } |
| #define netlink_ack netlink_ack_backport |
| #endif |
| |
| static struct sock *nl; |
| static u32 iscsid_pid; |
| |
| static int event_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) |
| { |
| u32 pid; |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0)) |
| pid = NETLINK_CB(skb).pid; |
| #else |
| pid = NETLINK_CB(skb).portid; |
| #endif |
| WARN_ON(pid == 0); |
| |
| iscsid_pid = pid; |
| |
| return 0; |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) |
| static int event_recv_skb(struct sk_buff *skb) |
| #else |
| static void event_recv_skb(struct sk_buff *skb) |
| #endif |
| { |
| int err; |
| struct nlmsghdr *nlh; |
| u32 rlen; |
| |
| while (skb->len >= NLMSG_SPACE(0)) { |
| nlh = (struct nlmsghdr *)skb->data; |
| if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len) |
| goto out; |
| rlen = NLMSG_ALIGN(nlh->nlmsg_len); |
| if (rlen > skb->len) |
| rlen = skb->len; |
| err = event_recv_msg(skb, nlh); |
| if (err) |
| netlink_ack(skb, nlh, -err, NULL); |
| else if (nlh->nlmsg_flags & NLM_F_ACK) |
| netlink_ack(skb, nlh, 0, NULL); |
| skb_pull(skb, rlen); |
| } |
| |
| out: |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) |
| return 0; |
| #else |
| return; |
| #endif |
| } |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) |
| static void event_recv(struct sock *sk, int length) |
| { |
| struct sk_buff *skb; |
| |
| while ((skb = skb_dequeue(&sk->sk_receive_queue))) { |
| if (event_recv_skb(skb) && skb->len) |
| skb_queue_head(&sk->sk_receive_queue, skb); |
| else |
| kfree_skb(skb); |
| } |
| } |
| #endif |
| |
| /* event_mutex supposed to be held */ |
| static int __event_send(const void *buf, int buf_len) |
| { |
| int res = 0, len; |
| struct sk_buff *skb; |
| struct nlmsghdr *nlh; |
| static u32 seq; /* protected by event_mutex */ |
| |
| TRACE_ENTRY(); |
| |
| if (ctr_open_state != ISCSI_CTR_OPEN_STATE_OPEN) |
| goto out; |
| |
| len = NLMSG_SPACE(buf_len); |
| |
| skb = alloc_skb(len, GFP_KERNEL); |
| if (skb == NULL) { |
| PRINT_ERROR("alloc_skb() failed (len %d)", len); |
| res = -ENOMEM; |
| goto out; |
| } |
| |
| nlh = __nlmsg_put(skb, iscsid_pid, seq++, NLMSG_DONE, buf_len, 0); |
| |
| memcpy(NLMSG_DATA(nlh), buf, buf_len); |
| res = netlink_unicast(nl, skb, iscsid_pid, 0); |
| if (res <= 0) { |
| if (res != -ECONNREFUSED) |
| PRINT_ERROR("netlink_unicast() failed: %d", res); |
| else |
| TRACE(TRACE_MINOR, |
| "netlink_unicast() failed: %s. Not functioning user space?", |
| "Connection refused"); |
| goto out; |
| } |
| |
| out: |
| TRACE_EXIT_RES(res); |
| return res; |
| } |
| |
| int event_send(u32 tid, u64 sid, u32 cid, u32 cookie, |
| enum iscsi_kern_event_code code, |
| const char *param1, const char *param2) |
| { |
| int err; |
| static DEFINE_MUTEX(event_mutex); |
| struct iscsi_kern_event event; |
| int param1_size, param2_size; |
| |
| param1_size = (param1 != NULL) ? strlen(param1) : 0; |
| param2_size = (param2 != NULL) ? strlen(param2) : 0; |
| |
| event.tid = tid; |
| event.sid = sid; |
| event.cid = cid; |
| event.code = code; |
| event.cookie = cookie; |
| event.param1_size = param1_size; |
| event.param2_size = param2_size; |
| |
| mutex_lock(&event_mutex); |
| |
| err = __event_send(&event, sizeof(event)); |
| if (err <= 0) |
| goto out_unlock; |
| |
| if (param1_size > 0) { |
| err = __event_send(param1, param1_size); |
| if (err <= 0) |
| goto out_unlock; |
| } |
| |
| if (param2_size > 0) { |
| err = __event_send(param2, param2_size); |
| if (err <= 0) |
| goto out_unlock; |
| } |
| |
| out_unlock: |
| mutex_unlock(&event_mutex); |
| return err; |
| } |
| |
| int __init event_init(void) |
| { |
| iscsi_net_ns = kobj_ns_grab_current(KOBJ_NS_TYPE_NET); |
| |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 22)) |
| nl = netlink_kernel_create(NETLINK_ISCSI_SCST, 1, event_recv, |
| THIS_MODULE); |
| #elif (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)) |
| nl = netlink_kernel_create(NETLINK_ISCSI_SCST, 1, event_recv, NULL, |
| THIS_MODULE); |
| #elif (LINUX_VERSION_CODE < KERNEL_VERSION(3, 6, 0)) |
| nl = netlink_kernel_create(iscsi_net_ns, NETLINK_ISCSI_SCST, 1, |
| event_recv_skb, NULL, THIS_MODULE); |
| #else |
| { |
| struct netlink_kernel_cfg cfg = { |
| .input = event_recv_skb, |
| .groups = 1, |
| }; |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0)) |
| nl = netlink_kernel_create(iscsi_net_ns, NETLINK_ISCSI_SCST, |
| THIS_MODULE, &cfg); |
| #else |
| nl = netlink_kernel_create(iscsi_net_ns, NETLINK_ISCSI_SCST, |
| &cfg); |
| #endif |
| } |
| #endif |
| if (!nl) |
| goto drop_ns; |
| |
| return 0; |
| |
| drop_ns: |
| PRINT_ERROR("%s", "netlink_kernel_create() failed"); |
| kobj_ns_drop(KOBJ_NS_TYPE_NET, iscsi_net_ns); |
| iscsi_net_ns = NULL; |
| return -ENOMEM; |
| } |
| |
| void event_exit(void) |
| { |
| #if (LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 24)) |
| if (nl) |
| sock_release(nl->sk_socket); |
| #else |
| netlink_kernel_release(nl); |
| #endif |
| kobj_ns_drop(KOBJ_NS_TYPE_NET, iscsi_net_ns); |
| iscsi_net_ns = NULL; |
| } |