I'm creating a Linux module for a game library that let's you hotplug multiple joysticks, it uses inotify to watch /dev/input
.
I am testing it with 3 joysticks:
perror
gives: /dev/input/js1: Permission denied
.ls -l /proc/<pid-of-process>/fd
it lists /dev/input/js0
and /dev/input/js2
.All the joysticks work fine when I run it as root.
This is how it's initialized:
static void createGamepad(char *locName){
char dirName[30];
int fd;
snprintf(dirName, 30, "/dev/input/%s", locName);
fd = open(dirName, O_RDONLY | O_NONBLOCK, 0);
if(fd < 0){
perror(dirName);
}
}
struct dirent *dir;
DIR *d;
int i, notifyfd, watch;
// Attach notifications to check if a device connects/disconnects
notifyfd = inotify_init();
watch = inotify_add_watch(notifyfd, "/dev/input", IN_CREATE | IN_DELETE);
d = opendir("/dev/input");
i = 0;
while((dir = readdir(d)) != NULL){
if(*dir->d_name == 'j' && *(dir->d_name + 1) == 's'){
createGamepad(dir->d_name, i);
i++;
}
}
closedir(d);
After that inotify handles it like this in the while(1)
loop:
static bool canReadINotify(){
fd_set set;
struct timeval timeout;
FD_ZERO(&set);
FD_SET(notifyfd, &set);
timeout.tv_sec = 0;
timeout.tv_usec = 0;
return select(notifyfd + 1, &set, NULL, NULL, &timeout) > 0 &&
FD_ISSET(notifyfd, &set);
}
// Inside the event loop
struct inotify_event ne;
while(canReadINotify()){
if(read(notifyfd, &ne, sizeof(struct inotify_event) + 16) >= 0){
if(*ne.name != 'j' || *(ne.name + 1) != 's'){
continue;
}
if(ne.mask & IN_CREATE){
createGamepad(ne.name);
}
}
}
Is it even possible with inotify or should I use udev? And if it's possible, how can I solve this?
It is very likely a race condition. You see, you get the inotify event when the device node is created (by udev using a mknod()
call), but the access permissions are set by udev using a separate chown()
call, just a tiny bit later.
See systemd src/udev/udev-node.c
, node_permissions_apply()
. In this particular case, /dev/input/jsX
is not a symlink, but the actual device node; at least with systemd the device node access mode gets set sometime later, after the actual node is created.
One robust solution would be to modify your createGamepad()
function, so that instead of failing completely at fd == -1 && errno == EACCES
, you instead retry after a short while; at least a few times, say for up to a second or two.
However, ninjalj pointed out a better suggestion: use also the access permissions change as a trigger to check the device node. This is trivially accomplished, by using IN_CREATE | IN_DELETE | IN_ATTRIBUTE
in the inotify_add_watch()
function!
(You'll also want to ignore open()==-1, errno==EACCES
errors in createGamepad()
, as they are likely caused by this race condition, and the following IN_ATTRIBUTE
inotify event will yield access to the same device.)
Prior to ninjalj's comment, I'd personally have used an array of input devices, and another for "possible" input devices that can/need to be retried after a short timeout to decide whether they are available or not, but I think his suggestion is much better.
Need/want an example?