/*** * * rtcfg/rtcfg_ioctl.c * * Real-Time Configuration Distribution Protocol * * Copyright (C) 2003-2005 Jan Kiszka * * 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 #include #include #include #include #include #include #include #include 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 };