blob: 3b33850efb1fde99b43f86ea37aa8f4cc3feae61 [file] [log] [blame]
/*
* Copyright (C) 2011-2014 Dropcam
* All rights reserved.
*
* 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; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <net/sock.h>
#include <net/netlink.h>
#include "fsprotodef.h"
#define LAST_RECV_BUF_SIZE 16384
//#define VERBOSE_LOGGING
static struct fsg_common *the_common;
static struct sock *fs_nl_sock;
static int fs_nl_seqnum;
static int fs_nl_last_recv;
static void *last_recv_buf;
static int last_recv_buf_len;
static int fs_nl_active(void) {
return (mod_data.fs_drv_pid != 0);
}
static int fs_intercept_io(loff_t length, loff_t offset) {
uint64_t intercept_start, intercept_end;
if (!fs_nl_active())
return 0;
intercept_start = mod_data.fs_drv_offset;
intercept_end = mod_data.fs_drv_offset + mod_data.fs_drv_length;
return offset >= intercept_start;
}
static void fs_nl_rcv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh = NULL;
if (the_common == NULL) {
printk(KERN_ERR "%s: common is NULL\n", __func__);
return;
}
if(skb == NULL) {
printk(KERN_ERR "%s: invalid sk_buff\n", __func__);
return;
}
nlh = (struct nlmsghdr *)skb->data;
if (nlh->nlmsg_len > LAST_RECV_BUF_SIZE) {
printk(KERN_ERR "%s: netlink message is too large\n", __func__);
return;
}
smp_wmb();
spin_lock(&the_common->lock);
fs_nl_last_recv = nlh->nlmsg_seq;
memcpy(last_recv_buf, nlh, nlh->nlmsg_len);
last_recv_buf_len = nlh->nlmsg_len;
#ifdef VERBOSE_LOGGING
printk(KERN_ERR "%s: recv nl_msg len: %d\n", __func__, (int)nlh->nlmsg_len);
#endif
wakeup_thread(the_common);
spin_unlock(&the_common->lock);
}
static int fs_create_netlink(void)
{
fs_nl_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0,
fs_nl_rcv_msg, NULL, THIS_MODULE);
if (!fs_nl_sock) {
printk(KERN_ERR "%s: receive handler registration failed\n", __func__);
return -ENOMEM;
}
last_recv_buf = kmalloc(LAST_RECV_BUF_SIZE, GFP_KERNEL);
return 0;
}
static int fs_release_netlink(void)
{
if (fs_nl_sock) {
netlink_kernel_release(fs_nl_sock);
fs_nl_sock = NULL;
}
if (last_recv_buf) {
kfree(last_recv_buf);
last_recv_buf = NULL;
}
return 0;
}
static int fs_register_common(struct fsg_common *fsg)
{
the_common = fsg;
return 0;
}
static int fs_unregister_common(void)
{
the_common = NULL;
return 0;
}
static int fs_msg_send(struct fsg_common *fsg, struct fsdrv_nl_hdr *hdr, void *payload)
{
struct sk_buff *skb_out;
struct nlmsghdr *nlh;
int res;
int total_msg_len = sizeof(*hdr) + hdr->payload_len;
skb_out = nlmsg_new(total_msg_len, 0);
nlh = nlmsg_put(skb_out, 0, ++fs_nl_seqnum, FSDRV_NL_MSG_TYPE, total_msg_len, 0);
NETLINK_CB(skb_out).dst_group = 0;
memcpy(nlmsg_data(nlh), hdr, sizeof(*hdr));
if (payload > 0) {
memcpy(nlmsg_data(nlh) + sizeof(*hdr), payload, hdr->payload_len);
}
res = nlmsg_unicast(fs_nl_sock, skb_out, mod_data.fs_drv_pid);
if(res < 0) {
printk(KERN_ERR "Error in nlmsg_unicast: %d\n", res);
/* zero out the PID so we stop trying to send to the process */
mod_data.fs_drv_pid = 0;
}
return res;
}
static int fs_msg_wait_for_resp(struct fsg_common *fsg)
{
int rc;
rc = 0;
/* wait for the response */
while (fs_nl_last_recv != fs_nl_seqnum) {
rc = sleep_thread(fsg);
if (rc)
return rc;
}
return rc;
}
static int fs_msg_read(struct fsg_common *fsg, void *buf, loff_t buf_len, loff_t file_offset)
{
int res;
struct fsdrv_nl_hdr hdr;
struct nlhdr *nlh;
struct fsdrv_nl_hdr *res_hdr;
#ifdef VERBOSE_LOGGING
printk(KERN_ERR "%s: %d, %d\n", __func__, (int)buf_len, (int)file_offset);
#endif
hdr.op = FSDRV_NL_MSG_READ_REQ;
hdr.payload_len = buf_len;
hdr.offset = file_offset;
/* issue the read request */
res = fs_msg_send(fsg, &hdr, buf);
if (res < 0) {
return res;
}
/* wait for the response */
res = fs_msg_wait_for_resp(fsg);
if (res < 0) {
return res;
}
/* response received - copy to the output buffer */
spin_lock(&fsg->lock);
nlh = (struct nlhdr*)last_recv_buf;
res_hdr = NLMSG_DATA(nlh);
memcpy(buf, NLMSG_DATA(nlh) + sizeof(struct fsdrv_nl_hdr), buf_len);
spin_unlock(&fsg->lock);
return buf_len;
}
static int fs_msg_write(struct fsg_common *fsg, void *buf, loff_t buf_len, loff_t file_offset)
{
int res;
struct fsdrv_nl_hdr hdr;
hdr.op = FSDRV_NL_MSG_WRITE_REQ;
hdr.payload_len = buf_len;
hdr.offset = file_offset;
#ifdef VERBOSE_LOGGING
printk(KERN_ERR "%s: %d, %d\n", __func__, (int)buf_len, (int)file_offset);
#endif
/* issue the write request */
res = fs_msg_send(fsg, &hdr, buf);
if (res < 0) {
return res;
}
/* wait for the response */
res = fs_msg_wait_for_resp(fsg);
if (res < 0) {
return res;
}
return buf_len;
}