Issue
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).
Answered By - Alex44