/***
|
*
|
* rtcfg/rtcfg_ioctl.c
|
*
|
* Real-Time Configuration Distribution Protocol
|
*
|
* Copyright (C) 2003-2005 Jan Kiszka <jan.kiszka@web.de>
|
*
|
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
|
*
|
*/
|
|
#include <linux/fs.h>
|
#include <linux/file.h>
|
#include <linux/vmalloc.h>
|
|
#include <rtcfg_chrdev.h>
|
#include <rtnet_rtpc.h>
|
#include <rtcfg/rtcfg_conn_event.h>
|
#include <rtcfg/rtcfg_event.h>
|
#include <rtcfg/rtcfg_frame.h>
|
#include <rtcfg/rtcfg_proc.h>
|
|
int rtcfg_event_handler(struct rt_proc_call *call)
|
{
|
struct rtcfg_cmd *cmd_event;
|
|
cmd_event = rtpc_get_priv(call, struct rtcfg_cmd);
|
return rtcfg_do_main_event(cmd_event->internal.data.ifindex,
|
cmd_event->internal.data.event_id, call);
|
}
|
|
void keep_cmd_add(struct rt_proc_call *call, void *priv_data)
|
{
|
/* do nothing on error (<0), or if file already present (=0) */
|
if (rtpc_get_result(call) <= 0)
|
return;
|
|
/* Don't cleanup any buffers, we are going to recycle them! */
|
rtpc_set_cleanup_handler(call, NULL);
|
}
|
|
void cleanup_cmd_add(void *priv_data)
|
{
|
struct rtcfg_cmd *cmd = (struct rtcfg_cmd *)priv_data;
|
void *buf;
|
|
/* unlock proc and update directory structure */
|
rtcfg_unlockwr_proc(cmd->internal.data.ifindex);
|
|
buf = cmd->args.add.conn_buf;
|
if (buf != NULL)
|
kfree(buf);
|
|
buf = cmd->args.add.stage1_data;
|
if (buf != NULL)
|
kfree(buf);
|
|
if (cmd->args.add.stage2_file != NULL) {
|
buf = cmd->args.add.stage2_file->buffer;
|
if (buf != NULL)
|
vfree(buf);
|
kfree(cmd->args.add.stage2_file);
|
}
|
}
|
|
void cleanup_cmd_del(void *priv_data)
|
{
|
struct rtcfg_cmd *cmd = (struct rtcfg_cmd *)priv_data;
|
void *buf;
|
|
/* unlock proc and update directory structure */
|
rtcfg_unlockwr_proc(cmd->internal.data.ifindex);
|
|
if (cmd->args.del.conn_buf != NULL) {
|
buf = cmd->args.del.conn_buf->stage1_data;
|
if (buf != NULL)
|
kfree(buf);
|
kfree(cmd->args.del.conn_buf);
|
}
|
|
if (cmd->args.del.stage2_file != NULL) {
|
buf = cmd->args.del.stage2_file->buffer;
|
if (buf != NULL)
|
vfree(buf);
|
kfree(cmd->args.del.stage2_file);
|
}
|
}
|
|
void copy_stage_1_data(struct rt_proc_call *call, void *priv_data)
|
{
|
struct rtcfg_cmd *cmd;
|
int result = rtpc_get_result(call);
|
|
if (result <= 0)
|
return;
|
|
cmd = rtpc_get_priv(call, struct rtcfg_cmd);
|
|
if (cmd->args.client.buffer_size < (size_t)result)
|
rtpc_set_result(call, -ENOSPC);
|
else if (copy_to_user(cmd->args.client.buffer,
|
cmd->args.client.rtskb->data, result) != 0)
|
rtpc_set_result(call, -EFAULT);
|
}
|
|
void cleanup_cmd_client(void *priv_data)
|
{
|
struct rtcfg_cmd *cmd = (struct rtcfg_cmd *)priv_data;
|
void *station_buf;
|
struct rtskb *rtskb;
|
|
station_buf = cmd->args.client.station_buf;
|
if (station_buf != NULL)
|
kfree(station_buf);
|
|
rtskb = cmd->args.client.rtskb;
|
if (rtskb != NULL)
|
kfree_rtskb(rtskb);
|
}
|
|
void copy_stage_2_data(struct rt_proc_call *call, void *priv_data)
|
{
|
struct rtcfg_cmd *cmd;
|
int result = rtpc_get_result(call);
|
struct rtskb *rtskb;
|
|
if (result <= 0)
|
return;
|
|
cmd = rtpc_get_priv(call, struct rtcfg_cmd);
|
|
if (cmd->args.announce.buffer_size < (size_t)result)
|
rtpc_set_result(call, -ENOSPC);
|
else {
|
rtskb = cmd->args.announce.rtskb;
|
do {
|
if (copy_to_user(cmd->args.announce.buffer, rtskb->data,
|
rtskb->len) != 0) {
|
rtpc_set_result(call, -EFAULT);
|
break;
|
}
|
cmd->args.announce.buffer += rtskb->len;
|
rtskb = rtskb->next;
|
} while (rtskb != NULL);
|
}
|
}
|
|
void cleanup_cmd_announce(void *priv_data)
|
{
|
struct rtcfg_cmd *cmd = (struct rtcfg_cmd *)priv_data;
|
struct rtskb *rtskb;
|
|
rtskb = cmd->args.announce.rtskb;
|
if (rtskb != NULL)
|
kfree_rtskb(rtskb);
|
}
|
|
void cleanup_cmd_detach(void *priv_data)
|
{
|
struct rtcfg_cmd *cmd = (struct rtcfg_cmd *)priv_data;
|
void *buf;
|
|
/* unlock proc and update directory structure */
|
rtcfg_unlockwr_proc(cmd->internal.data.ifindex);
|
|
if (cmd->args.detach.conn_buf) {
|
buf = cmd->args.detach.conn_buf->stage1_data;
|
if (buf != NULL)
|
kfree(buf);
|
kfree(cmd->args.detach.conn_buf);
|
}
|
|
if (cmd->args.detach.stage2_file != NULL) {
|
buf = cmd->args.detach.stage2_file->buffer;
|
if (buf)
|
vfree(buf);
|
kfree(cmd->args.detach.stage2_file);
|
}
|
|
if (cmd->args.detach.station_addr_list)
|
kfree(cmd->args.detach.station_addr_list);
|
|
if (cmd->args.detach.stage2_chain)
|
kfree_rtskb(cmd->args.detach.stage2_chain);
|
}
|
|
static int load_cfg_file(struct rtcfg_file *cfgfile, struct rtcfg_cmd *cmd)
|
{
|
size_t file_size = 0;
|
struct file *filp;
|
loff_t i_size;
|
int ret;
|
|
filp = filp_open(cfgfile->name, O_RDONLY, 0);
|
if (IS_ERR(filp))
|
return PTR_ERR(filp);
|
|
i_size = i_size_read(file_inode(filp));
|
if (i_size <= 0) {
|
/* allocate buffer even for empty files */
|
cfgfile->buffer = vmalloc(1);
|
} else {
|
cfgfile->buffer = NULL; /* Leave allocation to the kernel. */
|
ret = read_file_from_kernel(filp, &cfgfile->buffer,
|
i_size_read(file_inode(filp)),
|
&file_size, READING_UNKNOWN);
|
if (ret < 0) {
|
fput(filp);
|
return ret;
|
}
|
}
|
|
fput(filp);
|
cfgfile->size = file_size;
|
|
/* dispatch again, this time with new file attached */
|
return rtpc_dispatch_call(rtcfg_event_handler, 0, cmd,
|
sizeof(*cmd), NULL, cleanup_cmd_add);
|
}
|
|
int rtcfg_ioctl_add(struct rtnet_device *rtdev, struct rtcfg_cmd *cmd)
|
{
|
struct rtcfg_connection *conn_buf;
|
struct rtcfg_file *file = NULL;
|
void *data_buf;
|
size_t size;
|
int ret;
|
|
conn_buf = kmalloc(sizeof(struct rtcfg_connection), GFP_KERNEL);
|
if (conn_buf == NULL)
|
return -ENOMEM;
|
cmd->args.add.conn_buf = conn_buf;
|
|
data_buf = NULL;
|
size = cmd->args.add.stage1_size;
|
if (size > 0) {
|
/* check stage 1 data size */
|
if (sizeof(struct rtcfg_frm_stage_1_cfg) +
|
2 * RTCFG_ADDRSIZE_IP + size >
|
rtdev->get_mtu(rtdev, RTCFG_SKB_PRIO)) {
|
ret = -ESTAGE1SIZE;
|
goto err;
|
}
|
|
data_buf = kmalloc(size, GFP_KERNEL);
|
if (data_buf == NULL) {
|
ret = -ENOMEM;
|
goto err;
|
}
|
|
ret = copy_from_user(data_buf, cmd->args.add.stage1_data, size);
|
if (ret != 0) {
|
ret = -EFAULT;
|
goto err;
|
}
|
}
|
cmd->args.add.stage1_data = data_buf;
|
|
if (cmd->args.add.stage2_filename != NULL) {
|
size = strnlen_user(cmd->args.add.stage2_filename, PATH_MAX);
|
|
file = kmalloc(sizeof(struct rtcfg_file) + size, GFP_KERNEL);
|
if (file == NULL) {
|
ret = -ENOMEM;
|
goto err;
|
}
|
|
file->name = (char *)file + sizeof(struct rtcfg_file);
|
file->buffer = NULL;
|
|
ret = copy_from_user(
|
(void *)file + sizeof(struct rtcfg_file),
|
(const void *)cmd->args.add.stage2_filename, size);
|
if (ret != 0) {
|
ret = -EFAULT;
|
goto err;
|
}
|
}
|
cmd->args.add.stage2_file = file;
|
|
/* lock proc structure for modification */
|
rtcfg_lockwr_proc(cmd->internal.data.ifindex);
|
|
ret = rtpc_dispatch_call(rtcfg_event_handler, 0, cmd, sizeof(*cmd),
|
keep_cmd_add, cleanup_cmd_add);
|
|
/* load file if missing */
|
if (ret > 0) {
|
ret = load_cfg_file(file, cmd);
|
if (ret) {
|
rtcfg_unlockwr_proc(cmd->internal.data.ifindex);
|
goto err;
|
}
|
}
|
|
return ret;
|
|
err:
|
kfree(conn_buf);
|
if (data_buf != NULL)
|
kfree(data_buf);
|
if (file != NULL) {
|
if (file->buffer != NULL)
|
vfree(file->buffer);
|
kfree(file);
|
}
|
return ret;
|
}
|
|
int rtcfg_ioctl(struct rtnet_device *rtdev, unsigned int request,
|
unsigned long arg)
|
{
|
struct rtcfg_cmd cmd;
|
struct rtcfg_station *station_buf;
|
int ret;
|
|
ret = copy_from_user(&cmd, (void *)arg, sizeof(cmd));
|
if (ret != 0)
|
return -EFAULT;
|
|
cmd.internal.data.ifindex = rtdev->ifindex;
|
cmd.internal.data.event_id = _IOC_NR(request);
|
|
switch (request) {
|
case RTCFG_IOC_SERVER:
|
ret = rtpc_dispatch_call(rtcfg_event_handler, 0, &cmd,
|
sizeof(cmd), NULL, NULL);
|
break;
|
|
case RTCFG_IOC_ADD:
|
ret = rtcfg_ioctl_add(rtdev, &cmd);
|
break;
|
|
case RTCFG_IOC_DEL:
|
cmd.args.del.conn_buf = NULL;
|
cmd.args.del.stage2_file = NULL;
|
|
/* lock proc structure for modification
|
(unlock in cleanup_cmd_del) */
|
rtcfg_lockwr_proc(cmd.internal.data.ifindex);
|
|
ret = rtpc_dispatch_call(rtcfg_event_handler, 0, &cmd,
|
sizeof(cmd), NULL, cleanup_cmd_del);
|
break;
|
|
case RTCFG_IOC_WAIT:
|
ret = rtpc_dispatch_call(rtcfg_event_handler,
|
cmd.args.wait.timeout, &cmd,
|
sizeof(cmd), NULL, NULL);
|
break;
|
|
case RTCFG_IOC_CLIENT:
|
station_buf = kmalloc(sizeof(struct rtcfg_station) *
|
cmd.args.client.max_stations,
|
GFP_KERNEL);
|
if (station_buf == NULL)
|
return -ENOMEM;
|
cmd.args.client.station_buf = station_buf;
|
cmd.args.client.rtskb = NULL;
|
|
ret = rtpc_dispatch_call(rtcfg_event_handler,
|
cmd.args.client.timeout, &cmd,
|
sizeof(cmd), copy_stage_1_data,
|
cleanup_cmd_client);
|
break;
|
|
case RTCFG_IOC_ANNOUNCE:
|
cmd.args.announce.rtskb = NULL;
|
|
ret = rtpc_dispatch_call(rtcfg_event_handler,
|
cmd.args.announce.timeout, &cmd,
|
sizeof(cmd), copy_stage_2_data,
|
cleanup_cmd_announce);
|
break;
|
|
case RTCFG_IOC_READY:
|
ret = rtpc_dispatch_call(rtcfg_event_handler,
|
cmd.args.ready.timeout, &cmd,
|
sizeof(cmd), NULL, NULL);
|
break;
|
|
case RTCFG_IOC_DETACH:
|
do {
|
cmd.args.detach.conn_buf = NULL;
|
cmd.args.detach.stage2_file = NULL;
|
cmd.args.detach.station_addr_list = NULL;
|
cmd.args.detach.stage2_chain = NULL;
|
|
/* lock proc structure for modification
|
(unlock in cleanup_cmd_detach) */
|
rtcfg_lockwr_proc(cmd.internal.data.ifindex);
|
|
ret = rtpc_dispatch_call(rtcfg_event_handler, 0, &cmd,
|
sizeof(cmd), NULL,
|
cleanup_cmd_detach);
|
} while (ret == -EAGAIN);
|
break;
|
|
default:
|
ret = -ENOTTY;
|
}
|
|
return ret;
|
}
|
|
struct rtnet_ioctls rtcfg_ioctls = { .service_name = "RTcfg",
|
.ioctl_type = RTNET_IOC_TYPE_RTCFG,
|
.handler = rtcfg_ioctl };
|