normal socket ioctl to net interface ioctl

Write down for wifi framework.

Describe how the flow is transaferred from a normal socket to the specific network interface ioctl. 

 

An artical from Vipul Gupta

 

In general an ioctl call in a user program looks like ioctl(int fd, int command, (char *) argstruct). For ioctl calls related to the networking code (these are the only ones we will deal with in this note), the file descriptor fd is actually a socket descriptor returned by the socket() system call. The command could be any one of those listed in /usr/include/linux/sockios.h. These commands are subdivided into a number of categories depending on what aspect of networking they deal with:

 

changing the routing table (e.g. SIOCADDRT, SIOCDELRT),

reading/updating the ARP/RARP caches (e.g. SIOCDARP, SIOCSRARP),

generic functions related to network interfaces (e.g. SIOCGIFNAME, SIOCSIFADDR etc)

The Goodies directory contains a number of sample programs illustrating the use of networking ioctl calls. As you look at these programs, notice how the structure used for argstruct depends on the ioctl command type. For example, routing table related ioctls use the rtentry structure defined in /usr/include/linux/route.h (see adddefault.c for an example) and ARP related ioctls use the arpreq structure defined in /usr/include/linux/if_arp.h (see arpread.c).

 

Network interface related ioctl commands typically look like SIOCxIFyyyy where x is either S (set, write) or G (get, read). The getifinfo.c program uses such commands to read the IP address, hardware address, broadcast address and flags associated with a network interface. For these ioctls, the third argument is an ifreq structure which is defined in /usr/include/linux/if.h. In some cases, new ioctl commands may be needed in addition to those defined in sockios.h, e.g. the WaveLAN wireless networking card maintains information about wireless signal strength which may be of use to a user program. How should user programs be allowed access to this information? Our first instinct may be to define a new ioctl command in sockios.h, e.g. SIOCGIFWVLNSS (Get WaVeLaN Signal Strength). Unfortunately, this command makes no sense at all for other interfaces (e.g. loopback) and attempts to use this ioctl command on interfaces other than WaveLAN cards should be disallowed. What we need, then, is a mechanism to define interface specific ioctl commands. Luckily, the Linux OS already has built-in hooks for this purpose. If you look at sockios.h again, you will notice that each device has a predefined SIOCDEVPRIVATE ioctl command. The implementation of this command is left totally upto the person writing the corresponding device driver.

 

By convention, a user program invokes a device specific ioctl command as ioctl(sockid, SIOCDEVPRIVATE, (char *) &ifr) where ifr is defined as struct ifreq ifr. It fills ifr.ifr_name with the interface name associated with the device, e.g. on tether, the WaveLAN card is named eth1. Typically, a user program will also need to exchange command arguments and results with the kernel and that is done through the ifr.ifr_data field, e.g. the signal strength infromation for the WaveLAN card could be returned in this field. The Linux source code already includes two devices de4x5 and ewrk3 that define and implement device specific ioctl commands. The source code for these drivers is in de4x5.h, de4x5.c, ewrk3.h, ewrk3.c, (in /usr/src/linux/drivers/net/). Both drivers define their own private structures (struct ewrk3_ioctl and struct de4x5_ioctl) for exchanging information between user programs and the drivers. Before the ioctl call, the user program fills out the necessary fields in this structure and points ifr.ifr_data to it.

 

Before we go any further into the driver code for ewrk3 and de4x5, let us trace through various steps in the processing of an ioctl call. All interface-type ioctl requests (SIOCxIFyyyy and SIOCDEVPRIVATE) result in dev_ioctl() (in /usr/src/linux/net/core/dev.c) being called. This is just a wrapper and most of the real action is left for dev_ifsioc() (also in dev.c). About the only thing dev_ioctl() does is check whether a calling process has the appropriate permissions to issue the command (e.g. commands to alter the routing tables require root permissions). One of the first things dev_ifsioc() does is get the device structre (struct device defined in /usr/include/linux/netdevice.h) corresponding to the device named in ifr.ifr_name. This is followed by the code to implement generic interface commands (e.g. SIOCGIFADDR) inside a giant switch statement. The SIOCDEVPRIVATE command and any others with codes from 0x89F0 through 0x89FF end up in the default: branch for this switch. Here, the kernel checks to see if a device specific ioctl handler has been set up in the device structure. The handler is maintained as a function pointer in the do_ioctl field of the device structure. If the handler has been set, the kernel invokes it.

 

So, to implement device specific ioctls, all one needs to do is write a device specific ioctl handler and have the do_ioctl field in the corresponding device structure point to it. For the ewrk3 device, this function is called ewrk3_ioctl() (in ewrk3.c) and the corresponding device structure is initialized in ewrk3_init(). The ewrk3_ioctl() code clearly indicates the use of ifr.ifr_data for exchanging information between the device driver and user program. Note that this area of memory can be used for bidirectional information exchange. For example, in the ewrk3 driver code, the first two bytes of ifr.ifr_data are used to convey the specific action (e.g. EWRK3_SET_PROM, EWRK3_CLR_PROM) desired by the user (the driver implements multiple device specific commands that are all invoked through SIOCDEVPRIVATE). Also, and the buffer pointed to by the fifth byte in ifr.ifr_data is used to exchange other information (hardware address when using EWRK3_SET_HWADDR or EWRK3_GET_HWADDR).

 

As you go through ewrk3_ioctl(), keep in mind that normally a user process cannot directly access kernel memory. For this reason, two special procedures memcpy_tofs() and memcpy_fromfs() are provided to driver writers. The kernel procedure memcpy_tofs(arg1, arg2, arg3) copies arg3 bytes from starting address arg2 (in kernel space) to address arg1 (in user space). Similarly, memcpy_fromfs(arg1, arg2, arg3) copies arg3 bytes from starting address arg2 (in user space) to address arg1 (in kernel space). These procedures are preceded by calls to verify_area() to verify that the process has appropriate access permissions. Also notice the use of the printk() function to print debugging information. This function is similar to printf() but cannot handle floating point. The printf() function is not available to kernel code. The output generated by printk() is logged into /usr/adm/messages. For more information on these and related procedures, look at the section titled Supporting Functions in Michael K. Johnson's "Linux Kernel Hacker's Guide" which is accessible from the Linux Documentation Home Page.

 

Let me paste the code extracted from kernel 2.6.xx. 

asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)

{

      struct file * filp;

      int error = -EBADF;

      int fput_needed;

 

      filp = fget_light(fd, &fput_needed);

      if (!filp)

            goto out;

 

      error = security_file_ioctl(filp, cmd, arg);

      if (error)

            goto out_fput;

 

      error = vfs_ioctl(filp, fd, cmd, arg);

 out_fput:

      fput_light(filp, fput_needed);

 out:

      return error;

}

 

 

int vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd, unsigned long arg)

{

      unsigned int flag;

      int on, error = 0;

 

      switch (cmd) {

            case FIOCLEX:

                  set_close_on_exec(fd, 1);

                  break;

 

            case FIONCLEX:

                  set_close_on_exec(fd, 0);

                  break;

 

            case FIONBIO:

                  if ((error = get_user(on, (int __user *)arg)) != 0)

                        break;

                  flag = O_NONBLOCK;

#ifdef __sparc__

                  /* SunOS compatibility item. */

                  if(O_NONBLOCK != O_NDELAY)

                        flag |= O_NDELAY;

#endif

                  if (on)

                        filp->f_flags |= flag;

                  else

                        filp->f_flags &= ~flag;

                  break;

 

            case FIOASYNC:

                  if ((error = get_user(on, (int __user *)arg)) != 0)

                        break;

                  flag = on ? FASYNC : 0;

 

                  /* Did FASYNC state change ? */

                  if ((flag ^ filp->f_flags) & FASYNC) {

                        if (filp->f_op && filp->f_op->fasync) {

                              lock_kernel();

                              error = filp->f_op->fasync(fd, filp, on);

                              unlock_kernel();

                        }

                        else error = -ENOTTY;

                  }

                  if (error != 0)

                        break;

 

                  if (on)

                        filp->f_flags |= FASYNC;

                  else

                        filp->f_flags &= ~FASYNC;

                  break;

 

            case FIOQSIZE:

                  if (S_ISDIR(filp->f_path.dentry->d_inode->i_mode) ||

                      S_ISREG(filp->f_path.dentry->d_inode->i_mode) ||

                      S_ISLNK(filp->f_path.dentry->d_inode->i_mode)) {

                        loff_t res = inode_get_bytes(filp->f_path.dentry->d_inode);

                        error = copy_to_user((loff_t __user *)arg, &res, sizeof(res)) ? -EFAULT : 0;

                  }

                  else

                        error = -ENOTTY;

                  break;

            default:

                  if (S_ISREG(filp->f_path.dentry->d_inode->i_mode))

                        error = file_ioctl(filp, cmd, arg);

                  else

                        error = do_ioctl(filp, cmd, arg);

                  break;

      }

      return error;

}

 

static int file_ioctl(struct file *filp, unsigned int cmd,

            unsigned long arg)

{

      int error;

      int block;

      struct inode * inode = filp->f_path.dentry->d_inode;

      int __user *p = (int __user *)arg;

 

      switch (cmd) {

            case FIBMAP:

            {

                  struct address_space *mapping = filp->f_mapping;

                  int res;

                  /* do we support this mess? */

                  if (!mapping->a_ops->bmap)

                        return -EINVAL;

                  if (!capable(CAP_SYS_RAWIO))

                        return -EPERM;

                  if ((error = get_user(block, p)) != 0)

                        return error;

 

                  lock_kernel();

                  res = mapping->a_ops->bmap(mapping, block);

                  unlock_kernel();

                  return put_user(res, p);

            }

            case FIGETBSZ:

                  return put_user(inode->i_sb->s_blocksize, p);

            case FIONREAD:

                  return put_user(i_size_read(inode) - filp->f_pos, p);

      }

 

      return do_ioctl(filp, cmd, arg);

}

 

 

static long do_ioctl(struct file *filp, unsigned int cmd,

            unsigned long arg)

{

      int error = -ENOTTY;

      void *f;

 

      if (!filp->f_op)

            goto out;

 

      if (filp->f_op->unlocked_ioctl) {

            error = filp->f_op->unlocked_ioctl(filp, cmd, arg); // which is sock_ioctl for socket

            if (error == -ENOIOCTLCMD)

                  error = -EINVAL;

            goto out;

      } else if ((f = filp->f_op->ioctl)) {

            lock_kernel();

            if (!filp->f_op->ioctl) {

                  printk("%s: ioctl %p disappeared\n", __FUNCTION__, f);

                  print_symbol("symbol: %s\n", (unsigned long)f);

                  dump_stack();

            } else {

                  error = filp->f_op->ioctl(filp->f_path.dentry->d_inode,

                                      filp, cmd, arg);

            }

            unlock_kernel();

      }

 

 out:

      return error;

}

 

static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)

{

      struct socket *sock;

      void __user *argp = (void __user *)arg;

      int pid, err;

 

      sock = file->private_data;

      if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {

            err = dev_ioctl(cmd, argp);

      } else

#ifdef CONFIG_WIRELESS_EXT

      if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {

            err = dev_ioctl(cmd, argp);

      } else

#endif                        /* CONFIG_WIRELESS_EXT */

            switch (cmd) {

            case FIOSETOWN:

            case SIOCSPGRP:

                  err = -EFAULT;

                  if (get_user(pid, (int __user *)argp))

                        break;

                  err = f_setown(sock->file, pid, 1);

                  break;

            case FIOGETOWN:

            case SIOCGPGRP:

                  err = put_user(f_getown(sock->file),

                               (int __user *)argp);

                  break;

            case SIOCGIFBR:

            case SIOCSIFBR:

            case SIOCBRADDBR:

            case SIOCBRDELBR:

                  err = -ENOPKG;

                  if (!br_ioctl_hook)

                        request_module("bridge");

 

                  mutex_lock(&br_ioctl_mutex);

                  if (br_ioctl_hook)

                        err = br_ioctl_hook(cmd, argp);

                  mutex_unlock(&br_ioctl_mutex);

                  break;

            case SIOCGIFVLAN:

            case SIOCSIFVLAN:

                  err = -ENOPKG;

                  if (!vlan_ioctl_hook)

                        request_module("8021q");

 

                  mutex_lock(&vlan_ioctl_mutex);

                  if (vlan_ioctl_hook)

                        err = vlan_ioctl_hook(argp);

                  mutex_unlock(&vlan_ioctl_mutex);

                  break;

            case SIOCADDDLCI:

            case SIOCDELDLCI:

                  err = -ENOPKG;

                  if (!dlci_ioctl_hook)

                        request_module("dlci");

 

                  if (dlci_ioctl_hook) {

                        mutex_lock(&dlci_ioctl_mutex);

                        err = dlci_ioctl_hook(cmd, argp);

                        mutex_unlock(&dlci_ioctl_mutex);

                  }

                  break;

            default:

                  err = sock->ops->ioctl(sock, cmd, arg); // inet_ioctl for AF_INET

 

                  /*

                   * If this ioctl is unknown try to hand it down

                   * to the NIC driver.

                   */

                  if (err == -ENOIOCTLCMD)

                        err = dev_ioctl(cmd, argp);

                  break;

            }

      return err;

}

 

int dev_ioctl(unsigned int cmd, void __user *arg)

{

      struct ifreq ifr;

      int ret;

      char *colon;

 

      /* One special case: SIOCGIFCONF takes ifconf argument

         and requires shared lock, because it sleeps writing

         to user space.

       */

 

      if (cmd == SIOCGIFCONF) {

            rtnl_lock();

            ret = dev_ifconf((char __user *) arg);

            rtnl_unlock();

            return ret;

      }

      if (cmd == SIOCGIFNAME)

            return dev_ifname((struct ifreq __user *)arg);

 

      if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))

            return -EFAULT;

 

      ifr.ifr_name[IFNAMSIZ-1] = 0;

 

      colon = strchr(ifr.ifr_name, ':');

      if (colon)

            *colon = 0;

 

      /*

       *    See which interface the caller is talking about.

       */

 

      switch (cmd) {

            /*

             *    These ioctl calls:

             *    - can be done by all.

             *    - atomic and do not require locking.

             *    - return a value

             */

            case SIOCGIFFLAGS:

            case SIOCGIFMETRIC:

            case SIOCGIFMTU:

            case SIOCGIFHWADDR:

            case SIOCGIFSLAVE:

            case SIOCGIFMAP:

            case SIOCGIFINDEX:

            case SIOCGIFTXQLEN:

                  dev_load(ifr.ifr_name);

                  read_lock(&dev_base_lock);

                  ret = dev_ifsioc(&ifr, cmd);

                  read_unlock(&dev_base_lock);

                  if (!ret) {

                        if (colon)

                              *colon = ':';

                        if (copy_to_user(arg, &ifr,

                                     sizeof(struct ifreq)))

                              ret = -EFAULT;

                  }

                  return ret;

 

            case SIOCETHTOOL:

                  dev_load(ifr.ifr_name);

                  rtnl_lock();

                  ret = dev_ethtool(&ifr);

                  rtnl_unlock();

                  if (!ret) {

                        if (colon)

                              *colon = ':';

                        if (copy_to_user(arg, &ifr,

                                     sizeof(struct ifreq)))

                              ret = -EFAULT;

                  }

                  return ret;

 

            /*

             *    These ioctl calls:

             *    - require superuser power.

             *    - require strict serialization.

             *    - return a value

             */

            case SIOCGMIIPHY:

            case SIOCGMIIREG:

            case SIOCSIFNAME:

                  if (!capable(CAP_NET_ADMIN))

                        return -EPERM;

                  dev_load(ifr.ifr_name);

                  rtnl_lock();

                  ret = dev_ifsioc(&ifr, cmd);

                  rtnl_unlock();

                  if (!ret) {

                        if (colon)

                              *colon = ':';

                        if (copy_to_user(arg, &ifr,

                                     sizeof(struct ifreq)))

                              ret = -EFAULT;

                  }

                  return ret;

 

            /*

             *    These ioctl calls:

             *    - require superuser power.

             *    - require strict serialization.

             *    - do not return a value

             */

            case SIOCSIFFLAGS:

            case SIOCSIFMETRIC:

            case SIOCSIFMTU:

            case SIOCSIFMAP:

            case SIOCSIFHWADDR:

            case SIOCSIFSLAVE:

            case SIOCADDMULTI:

            case SIOCDELMULTI:

            case SIOCSIFHWBROADCAST:

            case SIOCSIFTXQLEN:

            case SIOCSMIIREG:

            case SIOCBONDENSLAVE:

            case SIOCBONDRELEASE:

            case SIOCBONDSETHWADDR:

            case SIOCBONDCHANGEACTIVE:

            case SIOCBRADDIF:

            case SIOCBRDELIF:

                  if (!capable(CAP_NET_ADMIN))

                        return -EPERM;

                  /* fall through */

            case SIOCBONDSLAVEINFOQUERY:

            case SIOCBONDINFOQUERY:

                  dev_load(ifr.ifr_name);

                  rtnl_lock();

                  ret = dev_ifsioc(&ifr, cmd);

                  rtnl_unlock();

                  return ret;

 

            case SIOCGIFMEM:

                  /* Get the per device memory space. We can add this but

                   * currently do not support it */

            case SIOCSIFMEM:

                  /* Set the per device memory buffer space.

                   * Not applicable in our case */

            case SIOCSIFLINK:

                  return -EINVAL;

 

            /*

             *    Unknown or private ioctl.

             */

            default:

                  if (cmd == SIOCWANDEV ||

                      (cmd >= SIOCDEVPRIVATE &&

                       cmd <= SIOCDEVPRIVATE + 15)) {

                        dev_load(ifr.ifr_name);

                        rtnl_lock();

                        ret = dev_ifsioc(&ifr, cmd);

                        rtnl_unlock();

                        if (!ret && copy_to_user(arg, &ifr,

                                           sizeof(struct ifreq)))

                              ret = -EFAULT;

                        return ret;

                  }

                  /* Take care of Wireless Extensions */

                  if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST)

                        return wext_handle_ioctl(&ifr, cmd, arg);

                  return -EINVAL;

      }

}

 

 

int wext_handle_ioctl(struct ifreq *ifr, unsigned int cmd,

                  void __user *arg)

{

      int ret;

 

      /* If command is `set a parameter', or

       * `get the encoding parameters', check if

       * the user has the right to do it */

      if ((IW_IS_SET(cmd) || cmd == SIOCGIWENCODE || cmd == SIOCGIWENCODEEXT)

          && !capable(CAP_NET_ADMIN))

            return -EPERM;

 

      dev_load(ifr->ifr_name);

      rtnl_lock();

      ret = wireless_process_ioctl(ifr, cmd);

      rtnl_unlock();

      if (IW_IS_GET(cmd) && copy_to_user(arg, ifr, sizeof(struct ifreq)))

            return -EFAULT;

      return ret;

}

 

/* ---------------------------------------------------------------- */

/*

 * Main IOCTl dispatcher.

 * Check the type of IOCTL and call the appropriate wrapper...

 */

static int wireless_process_ioctl(struct ifreq *ifr, unsigned int cmd)

{

      struct net_device *dev;

      iw_handler  handler;

 

      /* Permissions are already checked in dev_ioctl() before calling us.

       * The copy_to/from_user() of ifr is also dealt with in there */

 

      /* Make sure the device exist */

      if ((dev = __dev_get_by_name(ifr->ifr_name)) == NULL)

            return -ENODEV;

 

      /* A bunch of special cases, then the generic case...

       * Note that 'cmd' is already filtered in dev_ioctl() with

       * (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) */

      if (cmd == SIOCGIWSTATS)

            return ioctl_standard_call(dev, ifr, cmd,

                                 &iw_handler_get_iwstats);

 

      if (cmd == SIOCGIWPRIV && dev->wireless_handlers)

            return ioctl_standard_call(dev, ifr, cmd,

                                 &iw_handler_get_private);

 

      /* Basic check */

      if (!netif_device_present(dev))

            return -ENODEV;

 

      /* New driver API : try to find the handler */

      handler = get_handler(dev, cmd);

      if (handler) {

            /* Standard and private are not the same */

            if (cmd < SIOCIWFIRSTPRIV)

                  return ioctl_standard_call(dev, ifr, cmd, handler);

            else

                  return ioctl_private_call(dev, ifr, cmd, handler);

      }

      /* Old driver API : call driver ioctl handler */

      if (dev->do_ioctl)

            return dev->do_ioctl(dev, ifr, cmd);

      return -EOPNOTSUPP;

}

 

static iw_handler get_handler(struct net_device *dev, unsigned int cmd)

{

      /* Don't "optimise" the following variable, it will crash */

      unsigned int      index;            /* *MUST* be unsigned */

 

      /* Check if we have some wireless handlers defined */

      if (dev->wireless_handlers == NULL)

            return NULL;

 

      /* Try as a standard command */

      index = cmd - SIOCIWFIRST;

      if (index < dev->wireless_handlers->num_standard)

            return dev->wireless_handlers->standard[index];

 

      /* Try as a private command */

      index = cmd - SIOCIWFIRSTPRIV;

      if (index < dev->wireless_handlers->num_private)

            return dev->wireless_handlers->private[index];

 

      /* Not found */

      return NULL;

}

 

/************************** IOCTL SUPPORT **************************/

/*

 * The original user space API to configure all those Wireless Extensions

 * is through IOCTLs.

 * In there, we check if we need to call the new driver API (iw_handler)

 * or just call the driver ioctl handler.

 */

 

/* ---------------------------------------------------------------- */

/*

 * Wrapper to call a standard Wireless Extension handler.

 * We do various checks and also take care of moving data between

 * user space and kernel space.

 */

static int ioctl_standard_call(struct net_device *    dev,

                         struct ifreq *         ifr,

                         unsigned int           cmd,

                         iw_handler       handler)

{

      struct iwreq *                      iwr = (struct iwreq *) ifr;

      const struct iw_ioctl_description * descr;

      struct iw_request_info              info;

      int                           ret = -EINVAL;

 

      /* Get the description of the IOCTL */

      if ((cmd - SIOCIWFIRST) >= standard_ioctl_num)

            return -EOPNOTSUPP;

      descr = &(standard_ioctl[cmd - SIOCIWFIRST]);

 

      /* Prepare the call */

      info.cmd = cmd;

      info.flags = 0;

 

      /* Check if we have a pointer to user space data or not */

      if (descr->header_type != IW_HEADER_TYPE_POINT) {

 

            /* No extra arguments. Trivial to handle */

            ret = handler(dev, &info, &(iwr->u), NULL);

 

            /* Generate an event to notify listeners of the change */

            if ((descr->flags & IW_DESCR_FLAG_EVENT) &&

               ((ret == 0) || (ret == -EIWCOMMIT)))

                  wireless_send_event(dev, cmd, &(iwr->u), NULL);

      } else {

            char *      extra;

            int   extra_size;

            int   user_length = 0;

            int   err;

            int   essid_compat = 0;

 

            /* Calculate space needed by arguments. Always allocate

             * for max space. Easier, and won't last long... */

            extra_size = descr->max_tokens * descr->token_size;

 

            /* Check need for ESSID compatibility for WE < 21 */

            switch (cmd) {

            case SIOCSIWESSID:

            case SIOCGIWESSID:

            case SIOCSIWNICKN:

            case SIOCGIWNICKN:

                  if (iwr->u.data.length == descr->max_tokens + 1)

                        essid_compat = 1;

                  else if (IW_IS_SET(cmd) && (iwr->u.data.length != 0)) {

                        char essid[IW_ESSID_MAX_SIZE + 1];

 

                        err = copy_from_user(essid, iwr->u.data.pointer,

                                         iwr->u.data.length *

                                         descr->token_size);

                        if (err)

                              return -EFAULT;

 

                        if (essid[iwr->u.data.length - 1] == '\0')

                              essid_compat = 1;

                  }

                  break;

            default:

                  break;

            }

 

            iwr->u.data.length -= essid_compat;

 

            /* Check what user space is giving us */

            if (IW_IS_SET(cmd)) {

                  /* Check NULL pointer */

                  if ((iwr->u.data.pointer == NULL) &&

                     (iwr->u.data.length != 0))

                        return -EFAULT;

                  /* Check if number of token fits within bounds */

                  if (iwr->u.data.length > descr->max_tokens)

                        return -E2BIG;

                  if (iwr->u.data.length < descr->min_tokens)

                        return -EINVAL;

            } else {

                  /* Check NULL pointer */

                  if (iwr->u.data.pointer == NULL)

                        return -EFAULT;

                  /* Save user space buffer size for checking */

                  user_length = iwr->u.data.length;

 

                  /* Don't check if user_length > max to allow forward

                   * compatibility. The test user_length < min is

                   * implied by the test at the end. */

 

                  /* Support for very large requests */

                  if ((descr->flags & IW_DESCR_FLAG_NOMAX) &&

                     (user_length > descr->max_tokens)) {

                        /* Allow userspace to GET more than max so

                         * we can support any size GET requests.

                         * There is still a limit : -ENOMEM. */

                        extra_size = user_length * descr->token_size;

                       /* Note : user_length is originally a __u16,

                         * and token_size is controlled by us,

                         * so extra_size won't get negative and

                         * won't overflow... */

                  }

            }

 

            /* Create the kernel buffer */

            /*    kzalloc ensures NULL-termination for essid_compat */

            extra = kzalloc(extra_size, GFP_KERNEL);

            if (extra == NULL)

                  return -ENOMEM;

 

            /* If it is a SET, get all the extra data in here */

            if (IW_IS_SET(cmd) && (iwr->u.data.length != 0)) {

                  err = copy_from_user(extra, iwr->u.data.pointer,

                                   iwr->u.data.length *

                                   descr->token_size);

                  if (err) {

                        kfree(extra);

                        return -EFAULT;

                  }

            }

 

            /* Call the handler */

            ret = handler(dev, &info, &(iwr->u), extra);

 

            iwr->u.data.length += essid_compat;

 

            /* If we have something to return to the user */

            if (!ret && IW_IS_GET(cmd)) {

                  /* Check if there is enough buffer up there */

                  if (user_length < iwr->u.data.length) {

                        kfree(extra);

                        return -E2BIG;

                  }

 

                  err = copy_to_user(iwr->u.data.pointer, extra,

                                 iwr->u.data.length *

                                 descr->token_size);

                  if (err)

                        ret =  -EFAULT;

            }

 

            /* Generate an event to notify listeners of the change */

            if ((descr->flags & IW_DESCR_FLAG_EVENT) &&

               ((ret == 0) || (ret == -EIWCOMMIT))) {

                  if (descr->flags & IW_DESCR_FLAG_RESTRICT)

                        /* If the event is restricted, don't

                         * export the payload */

                        wireless_send_event(dev, cmd, &(iwr->u), NULL);

                  else

                        wireless_send_event(dev, cmd, &(iwr->u),

                                        extra);

            }

 

            /* Cleanup - I told you it wasn't that long ;-) */

            kfree(extra);

      }

 

      /* Call commit handler if needed and defined */

      if (ret == -EIWCOMMIT)

            ret = call_commit_handler(dev);

 

      /* Here, we will generate the appropriate event if needed */

 

      return ret;

}

 

/* ---------------------------------------------------------------- */

/*

 * Wrapper to call a private Wireless Extension handler.

 * We do various checks and also take care of moving data between

 * user space and kernel space.

 * It's not as nice and slimline as the standard wrapper. The cause

 * is struct iw_priv_args, which was not really designed for the

 * job we are going here.

 *

 * IMPORTANT : This function prevent to set and get data on the same

 * IOCTL and enforce the SET/GET convention. Not doing it would be

 * far too hairy...

 * If you need to set and get data at the same time, please don't use

 * a iw_handler but process it in your ioctl handler (i.e. use the

 * old driver API).

 */

static int ioctl_private_call(struct net_device *dev, struct ifreq *ifr,

                        unsigned int cmd, iw_handler handler)

{

      struct iwreq *                iwr = (struct iwreq *) ifr;

      const struct iw_priv_args *   descr = NULL;

      struct iw_request_info        info;

      int                     extra_size = 0;

      int                     i;

      int                     ret = -EINVAL;

 

      /* Get the description of the IOCTL */

      for (i = 0; i < dev->wireless_handlers->num_private_args; i++)

            if (cmd == dev->wireless_handlers->private_args[i].cmd) {

                  descr = &(dev->wireless_handlers->private_args[i]);

                  break;

            }

 

      /* Compute the size of the set/get arguments */

      if (descr != NULL) {

            if (IW_IS_SET(cmd)) {

                  int   offset = 0; /* For sub-ioctls */

                  /* Check for sub-ioctl handler */

                  if (descr->name[0] == '\0')

                        /* Reserve one int for sub-ioctl index */

                        offset = sizeof(__u32);

 

                  /* Size of set arguments */

                  extra_size = get_priv_size(descr->set_args);

 

                  /* Does it fits in iwr ? */

                  if ((descr->set_args & IW_PRIV_SIZE_FIXED) &&

                     ((extra_size + offset) <= IFNAMSIZ))

                        extra_size = 0;

            } else {

                  /* Size of get arguments */

                  extra_size = get_priv_size(descr->get_args);

 

                  /* Does it fits in iwr ? */

                  if ((descr->get_args & IW_PRIV_SIZE_FIXED) &&

                     (extra_size <= IFNAMSIZ))

                        extra_size = 0;

            }

      }

 

      /* Prepare the call */

      info.cmd = cmd;

      info.flags = 0;

 

      /* Check if we have a pointer to user space data or not. */

      if (extra_size == 0) {

            /* No extra arguments. Trivial to handle */

            ret = handler(dev, &info, &(iwr->u), (char *) &(iwr->u));

      } else {

            char *      extra;

            int   err;

 

            /* Check what user space is giving us */

            if (IW_IS_SET(cmd)) {

                  /* Check NULL pointer */

                  if ((iwr->u.data.pointer == NULL) &&

                     (iwr->u.data.length != 0))

                        return -EFAULT;

 

                  /* Does it fits within bounds ? */

                  if (iwr->u.data.length > (descr->set_args &

                                     IW_PRIV_SIZE_MASK))

                        return -E2BIG;

            } else if (iwr->u.data.pointer == NULL)

                  return -EFAULT;

 

            /* Always allocate for max space. Easier, and won't last

             * long... */

            extra = kmalloc(extra_size, GFP_KERNEL);

            if (extra == NULL)

                  return -ENOMEM;

 

            /* If it is a SET, get all the extra data in here */

            if (IW_IS_SET(cmd) && (iwr->u.data.length != 0)) {

                  err = copy_from_user(extra, iwr->u.data.pointer,

                                   extra_size);

                  if (err) {

                        kfree(extra);

                        return -EFAULT;

                  }

            }

 

            /* Call the handler */

            ret = handler(dev, &info, &(iwr->u), extra);

 

            /* If we have something to return to the user */

            if (!ret && IW_IS_GET(cmd)) {

 

                  /* Adjust for the actual length if it's variable,

                   * avoid leaking kernel bits outside. */

                  if (!(descr->get_args & IW_PRIV_SIZE_FIXED)) {

                        extra_size = adjust_priv_size(descr->get_args,

                                                &(iwr->u));

                  }

 

                  err = copy_to_user(iwr->u.data.pointer, extra,

                                 extra_size);

                  if (err)

                        ret =  -EFAULT;

            }

 

            /* Cleanup - I told you it wasn't that long ;-) */

            kfree(extra);

      }

 

 

      /* Call commit handler if needed and defined */

      if (ret == -EIWCOMMIT)

            ret = call_commit_handler(dev);

 

      return ret;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值