mirror of https://github.com/encounter/SDL.git
joystick: Use inotify to detect joystick unplug if not using udev
This improves SDL's ability to detect joystick hotplug in a container environment. We cannot reliably receive events from udev in a container, because they are delivered as netlink events, which are authenticated by their uid being 0. However, in a user namespace created by an unprivileged user (for example bubblewrap, as used by Flatpak and Steam's pressure-vessel-wrap), the kernel does not allow us to map uid 0, and the netlink events appear to be from the kernel's overflowuid (typically 65534/nobody), meaning libudev cannot distinguish between genuine uevents from udevd and an attack by a malicious local user. Signed-off-by: Simon McVittie <smcv@collabora.com>
This commit is contained in:
parent
aae53d5972
commit
b0eba1c55d
|
@ -32,6 +32,7 @@
|
||||||
#include <errno.h> /* errno, strerror */
|
#include <errno.h> /* errno, strerror */
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <limits.h> /* For the definition of PATH_MAX */
|
#include <limits.h> /* For the definition of PATH_MAX */
|
||||||
|
#include <sys/inotify.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
#include "SDL_assert.h"
|
#include "SDL_assert.h"
|
||||||
#include "SDL_hints.h"
|
#include "SDL_hints.h"
|
||||||
#include "SDL_joystick.h"
|
#include "SDL_joystick.h"
|
||||||
|
#include "SDL_log.h"
|
||||||
#include "SDL_endian.h"
|
#include "SDL_endian.h"
|
||||||
#include "SDL_timer.h"
|
#include "SDL_timer.h"
|
||||||
#include "../../events/SDL_events_c.h"
|
#include "../../events/SDL_events_c.h"
|
||||||
|
@ -92,16 +94,10 @@ typedef enum
|
||||||
ENUMERATION_FALLBACK
|
ENUMERATION_FALLBACK
|
||||||
} EnumerationMethod;
|
} EnumerationMethod;
|
||||||
|
|
||||||
#if SDL_USE_LIBUDEV
|
|
||||||
static EnumerationMethod enumeration_method = ENUMERATION_UNSET;
|
static EnumerationMethod enumeration_method = ENUMERATION_UNSET;
|
||||||
#else
|
|
||||||
const EnumerationMethod enumeration_method = ENUMERATION_FALLBACK;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static int MaybeAddDevice(const char *path);
|
static int MaybeAddDevice(const char *path);
|
||||||
#if SDL_USE_LIBUDEV
|
|
||||||
static int MaybeRemoveDevice(const char *path);
|
static int MaybeRemoveDevice(const char *path);
|
||||||
#endif /* SDL_USE_LIBUDEV */
|
|
||||||
|
|
||||||
/* A linked list of available joysticks */
|
/* A linked list of available joysticks */
|
||||||
typedef struct SDL_joylist_item
|
typedef struct SDL_joylist_item
|
||||||
|
@ -121,6 +117,7 @@ typedef struct SDL_joylist_item
|
||||||
static SDL_joylist_item *SDL_joylist = NULL;
|
static SDL_joylist_item *SDL_joylist = NULL;
|
||||||
static SDL_joylist_item *SDL_joylist_tail = NULL;
|
static SDL_joylist_item *SDL_joylist_tail = NULL;
|
||||||
static int numjoysticks = 0;
|
static int numjoysticks = 0;
|
||||||
|
static int inotify_fd = -1;
|
||||||
|
|
||||||
static Uint32 last_joy_detect_time;
|
static Uint32 last_joy_detect_time;
|
||||||
static time_t last_input_dir_mtime;
|
static time_t last_input_dir_mtime;
|
||||||
|
@ -188,7 +185,7 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid)
|
||||||
char product_string[128];
|
char product_string[128];
|
||||||
|
|
||||||
/* When udev is enabled we only get joystick devices here, so there's no need to test them */
|
/* When udev is enabled we only get joystick devices here, so there's no need to test them */
|
||||||
if (enumeration_method == ENUMERATION_FALLBACK && !GuessIsJoystick(fd)) {
|
if (enumeration_method != ENUMERATION_LIBUDEV && !GuessIsJoystick(fd)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,6 +500,75 @@ static void SteamControllerDisconnectedCallback(int device_instance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
StrHasPrefix(const char *string, const char *prefix)
|
||||||
|
{
|
||||||
|
return (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
StrIsInteger(const char *string)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
if (*string == '\0') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (p = string; *p != '\0'; p++) {
|
||||||
|
if (*p < '0' || *p > '9') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
LINUX_InotifyJoystickDetect(void)
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct inotify_event event;
|
||||||
|
char storage[4096];
|
||||||
|
char enough_for_inotify[sizeof (struct inotify_event) + NAME_MAX + 1];
|
||||||
|
} buf;
|
||||||
|
ssize_t bytes;
|
||||||
|
size_t remain = 0;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
bytes = read(inotify_fd, &buf, sizeof (buf));
|
||||||
|
|
||||||
|
if (bytes > 0) {
|
||||||
|
remain = (size_t) bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (remain > 0) {
|
||||||
|
if (buf.event.len > 0) {
|
||||||
|
if (StrHasPrefix(buf.event.name, "event") &&
|
||||||
|
StrIsInteger(buf.event.name + strlen ("event"))) {
|
||||||
|
char path[PATH_MAX];
|
||||||
|
|
||||||
|
SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", buf.event.name);
|
||||||
|
|
||||||
|
if (buf.event.mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB)) {
|
||||||
|
MaybeAddDevice(path);
|
||||||
|
}
|
||||||
|
else if (buf.event.mask & (IN_DELETE | IN_MOVED_FROM)) {
|
||||||
|
MaybeRemoveDevice(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len = sizeof (struct inotify_event) + buf.event.len;
|
||||||
|
remain -= len;
|
||||||
|
|
||||||
|
if (remain != 0) {
|
||||||
|
memmove (&buf.storage[0], &buf.storage[len], remain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
LINUX_FallbackJoystickDetect(void)
|
LINUX_FallbackJoystickDetect(void)
|
||||||
{
|
{
|
||||||
|
@ -547,7 +613,10 @@ LINUX_JoystickDetect(void)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
if (inotify_fd >= 0) {
|
||||||
|
LINUX_InotifyJoystickDetect();
|
||||||
|
}
|
||||||
|
else {
|
||||||
LINUX_FallbackJoystickDetect();
|
LINUX_FallbackJoystickDetect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,6 +658,10 @@ LINUX_JoystickInit(void)
|
||||||
SDL_InitSteamControllers(SteamControllerConnectedCallback,
|
SDL_InitSteamControllers(SteamControllerConnectedCallback,
|
||||||
SteamControllerDisconnectedCallback);
|
SteamControllerDisconnectedCallback);
|
||||||
|
|
||||||
|
/* Force immediate joystick detection if using fallback */
|
||||||
|
last_joy_detect_time = 0;
|
||||||
|
last_input_dir_mtime = 0;
|
||||||
|
|
||||||
#if SDL_USE_LIBUDEV
|
#if SDL_USE_LIBUDEV
|
||||||
if (enumeration_method == ENUMERATION_LIBUDEV) {
|
if (enumeration_method == ENUMERATION_LIBUDEV) {
|
||||||
if (SDL_UDEV_Init() < 0) {
|
if (SDL_UDEV_Init() < 0) {
|
||||||
|
@ -607,9 +680,28 @@ LINUX_JoystickInit(void)
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
/* Force immediate joystick detection */
|
inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||||
last_joy_detect_time = 0;
|
|
||||||
last_input_dir_mtime = 0;
|
if (inotify_fd < 0) {
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
|
||||||
|
"Unable to initialize inotify, falling back to polling: %s",
|
||||||
|
strerror (errno));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* We need to watch for attribute changes in addition to
|
||||||
|
* creation, because when a device is first created, it has
|
||||||
|
* permissions that we can't read. When udev chmods it to
|
||||||
|
* something that we maybe *can* read, we'll get an
|
||||||
|
* IN_ATTRIB event to tell us. */
|
||||||
|
if (inotify_add_watch(inotify_fd, "/dev/input",
|
||||||
|
IN_CREATE | IN_DELETE | IN_MOVE | IN_ATTRIB) < 0) {
|
||||||
|
close(inotify_fd);
|
||||||
|
inotify_fd = -1;
|
||||||
|
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
|
||||||
|
"Unable to add inotify watch, falling back to polling: %s",
|
||||||
|
strerror (errno));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Report all devices currently present */
|
/* Report all devices currently present */
|
||||||
LINUX_JoystickDetect();
|
LINUX_JoystickDetect();
|
||||||
|
@ -1224,6 +1316,9 @@ LINUX_JoystickQuit(void)
|
||||||
SDL_joylist_item *item = NULL;
|
SDL_joylist_item *item = NULL;
|
||||||
SDL_joylist_item *next = NULL;
|
SDL_joylist_item *next = NULL;
|
||||||
|
|
||||||
|
close(inotify_fd);
|
||||||
|
inotify_fd = -1;
|
||||||
|
|
||||||
for (item = SDL_joylist; item; item = next) {
|
for (item = SDL_joylist; item; item = next) {
|
||||||
next = item->next;
|
next = item->next;
|
||||||
SDL_free(item->path);
|
SDL_free(item->path);
|
||||||
|
|
Loading…
Reference in New Issue