Search code examples
clinux-kernelkernelioctl

How to check for existing argument on ioctl


I want to try out how to use i/o-controls of a loadable kernel module, here a character device. The question is: How to check if the ioctl call on userspace side has an argument or not. I found that on ioctl an argument is optional.

Inside the userspace function:

// set a parameter - this is a proper call
if( ioctl(fd, IOCTL_SET_PARAM1, 5)<0 )
{
  fprintf(stderr,"Error while ioctl: %s\n", strerror(errno));
}

// call a setter without argument - this should cause an error
if( ioctl(fd, IOCTL_SET_PARAM1)<0 )
{
  fprintf(stderr,"Error while ioctl: %s\n", strerror(errno));
}

The corresponding kernel module handler:

long
fops_unlocked_ioctl (struct file    *p_file,
                     unsigned int    cmd,
                     unsigned long   arg)
{
  switch(cmd)
  {
  case IOCTL_SET_PARAM1:
    printk(KERN_INFO "IOCTL called with IOCTL_SET_PARAM1\n");
    if( /* how to check for the argument here? */ )
    {
      printk(KERN_WARNING "Missing argument\n");
      return -EINVAL;
    }
    param1 = (unsigned short) arg;
    printk(KERN_INFO "param1 set to %d\n",param1);
    break;
  default:
    printk(KERN_WARNING "IOCTL called with wrong request code.\n");
    return -EINVAL;
  }
  return 0;
}

Regards, Alex


Solution

  • Jonathan Leffler is fully right then he say, that the user should know what to do and what not.

    By the way. I worked out an solution which replaces the argument by an array with the count and the originally argument. On userspace side nothing change for the user. On kernelspace side there are two macros to get the originally argument and the number of arguments.

    Here comes the code:

    expdev.h

    #ifndef EXPDEV_H_INCLUDED
    #define EXPDEV_H_INCLUDED
    
    #ifndef __KERNEL__
      #include <stdint.h>
    #endif // __KERNEL__
    
    #include "pp_narg.h"
    
    enum {
      IOCTL_SET_PARAM1,
    };
    
    /* ioctl - wrapper *************************************************** */
    
    #ifndef __KERNEL__
      long int argW[2]={0};
    
      long int PP_IOCTL_WRAPARG( long int narg, long int arg )
      {
        argW[0] = narg;
        argW[1] = arg;
        return (long int)(argW);
      }
    
      #define PP_IOCTL_NARG(n,fd,cmd,arg,...) \
        ( (n==3) ? \
          ( ioctl(fd,cmd,(long int)(PP_IOCTL_WRAPARG(1,(long int)arg))) ) : \
          ( ioctl(fd,cmd,(long int)(PP_IOCTL_WRAPARG(0,(long int)arg))) ) )
    
      #define ioctl(...) \
        (PP_IOCTL_NARG(PP_NARG(__VA_ARGS__),         \
                       __VA_ARGS__, (long int)(0),(long int)(0)))
    
    #else // __KERNEL__
    
      #define IOCTL_ARGC(argW) (((long int*)argW)[0])
      #define IOCTL_ARG(argW)  (((long int*)argW)[1])
    
    #endif // __KERNEL__
    
    #endif // EXPDEV_H_INCLUDED
    

    For counting the arguments I follow this post.

    pp_narg.h

    /*
     Source: https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s
     */
    
    #ifndef PP_NARG_INCLUDED
    #define PP_NARG_INCLUDED
    
    #define PP_NARG(...) \
             PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
    #define PP_NARG_(...) \
             PP_ARG_N(__VA_ARGS__)
    #define PP_ARG_N( \
              _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
             _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
             _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
             _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
             _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
             _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
             _61,_62,_63,N,...) N
    #define PP_RSEQ_N() \
             63,62,61,60,                   \
             59,58,57,56,55,54,53,52,51,50, \
             49,48,47,46,45,44,43,42,41,40, \
             39,38,37,36,35,34,33,32,31,30, \
             29,28,27,26,25,24,23,22,21,20, \
             19,18,17,16,15,14,13,12,11,10, \
             9,8,7,6,5,4,3,2,1,0
    
    #endif // PP_NARG_INCLUDED
    

    Now a call inside a userspace function (same as usual):

    #include "expdev.h"
    
    // set a parameter
    if( ioctl(fd, IOCTL_SET_PARAM1, 5)<0 )
    {
      fprintf(stderr,"Error while ioctl: %s\n", strerror(errno));
    }
    
    // call a setter without argument - this should cause an error
    if( ioctl(fd, IOCTL_SET_PARAM1)<0 )
    {
      fprintf(stderr,"Error while ioctl: %s\n", strerror(errno));
    }
    

    and the kernelspace side (mind IOCTL_ARGC(arg) and IOCTL_ARG(arg)):

    #include "expdev.h"
    
    long
    fops_unlocked_ioctl (struct file    *p_file,
                         unsigned int    cmd,
                         unsigned long   arg)
    {
      switch(cmd)
      {
      case IOCTL_SET_PARAM1:
        printk(KERN_INFO "IOCTL called with IOCTL_SET_PARAM1\n");
        if( IOCTL_ARGC(arg)!=1 )
        {
          printk(KERN_ERR "Missing argument.\n");
          return -EINVAL;
        }
        param1 = (unsigned short) IOCTL_ARG(arg);
        printk(KERN_INFO "param1 set to %d\n",param1);
        break;
      default:
        printk(KERN_WARNING "IOCTL called with wrong request code.\n");
        return -EINVAL;
      }
      return 0;
    }
    

    This leads to the following output on the kern.log:

    kernel: [18577.042438] IOCTL called with IOCTL_SET_PARAM1
    kernel: [18577.042439] param1 set to 5
    kernel: [18577.042442] IOCTL called with IOCTL_SET_PARAM1
    kernel: [18577.042443] Missing argument.
    

    and on userspace side:

    Error while ioctl: Invalid argument
    

    NOTE:

    The macros redefine ioctl. This means using ioctl on different devices inside the same source will cause errors on the device which doesn't support this macro (for example if you use a standard tty).