// This file contains the private implementation of the open() hook // which must be in a separate compilation unit due to fcntl.h doing // unwanted redirection of open() to open64(). #define _GNU_SOURCE #include "SDL_compat.h" #include #include #include #include #include #include #include #include // __OPEN_NEEDS_MODE is a glibc-ism, so define it ourselves for other libc #ifndef __OPEN_NEEDS_MODE #ifdef __O_TMPFILE # define __OPEN_NEEDS_MODE(oflag) \ (((oflag) & O_CREAT) != 0 || ((oflag) & __O_TMPFILE) == __O_TMPFILE) #else # define __OPEN_NEEDS_MODE(oflag) (((oflag) & O_CREAT) != 0) #endif #endif #if SDL_VERSION_ATLEAST(2, 0, 15) extern int g_QtDrmMasterFd; extern struct stat g_DrmMasterStat; #define MAX_SDL_FD_COUNT 8 int g_SdlDrmMasterFds[MAX_SDL_FD_COUNT]; int g_SdlDrmMasterFdCount = 0; SDL_SpinLock g_FdTableLock = 0; // Caller must hold g_FdTableLock int getSdlFdEntryIndex(bool unused) { for (int i = 0; i < MAX_SDL_FD_COUNT; i++) { // We slightly bend the FD rules here by treating 0 // as invalid since that's our global default value. if (unused && g_SdlDrmMasterFds[i] <= 0) { return i; } else if (!unused && g_SdlDrmMasterFds[i] > 0) { return i; } } return -1; } // Returns true if the final SDL FD was removed bool removeSdlFd(int fd) { SDL_AtomicLock(&g_FdTableLock); if (g_SdlDrmMasterFdCount != 0) { // Clear the entry for this fd from the table for (int i = 0; i < MAX_SDL_FD_COUNT; i++) { if (fd == g_SdlDrmMasterFds[i]) { g_SdlDrmMasterFds[i] = -1; g_SdlDrmMasterFdCount--; break; } } if (g_SdlDrmMasterFdCount == 0) { SDL_AtomicUnlock(&g_FdTableLock); return true; } } SDL_AtomicUnlock(&g_FdTableLock); return false; } // Returns the previous master FD or -1 if none int takeMasterFromSdlFd() { int fd = -1; // Since all SDL FDs are actually dups of each other // we can take master from any one of them. SDL_AtomicLock(&g_FdTableLock); int fdIndex = getSdlFdEntryIndex(false); if (fdIndex != -1) { fd = g_SdlDrmMasterFds[fdIndex]; } SDL_AtomicUnlock(&g_FdTableLock); if (fd >= 0 && drmDropMaster(fd) == 0) { return fd; } else { return -1; } } int openHook(const char *funcname, const char *pathname, int flags, va_list va) { int fd; mode_t mode; // Call the real thing to do the open operation if (__OPEN_NEEDS_MODE(flags)) { mode = va_arg(va, mode_t); fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode); } else { mode = 0; fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags); } // If the file was successfully opened and we have a DRM master FD, // check if the FD we just opened is a DRM device. if (fd >= 0 && g_QtDrmMasterFd != -1) { if (strncmp(pathname, "/dev/dri/card", 13) == 0) { // It's a DRM device, but is it _our_ DRM device? struct stat fdstat; fstat(fd, &fdstat); if (g_DrmMasterStat.st_dev == fdstat.st_dev && g_DrmMasterStat.st_ino == fdstat.st_ino) { int freeFdIndex; int allocatedFdIndex; // It is our device. Time to do the magic! SDL_AtomicLock(&g_FdTableLock); // Get a free index for us to put the new entry freeFdIndex = getSdlFdEntryIndex(true); if (freeFdIndex < 0) { SDL_AtomicUnlock(&g_FdTableLock); SDL_assert(freeFdIndex >= 0); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "No unused SDL FD table entries!"); // Hope for the best return fd; } // Check if we have an allocated entry already allocatedFdIndex = getSdlFdEntryIndex(false); if (allocatedFdIndex >= 0) { // Close fd that we opened earlier (skipping our close() hook) ((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd); // dup() an existing FD into the unused slot fd = dup(g_SdlDrmMasterFds[allocatedFdIndex]); } else { // Drop master on Qt's FD so we can pick it up for SDL. if (drmDropMaster(g_QtDrmMasterFd) < 0) { SDL_AtomicUnlock(&g_FdTableLock); SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to drop master on Qt DRM FD: %d", errno); // Hope for the best return fd; } // Close fd that we opened earlier (skipping our close() hook) ((typeof(close)*)dlsym(RTLD_NEXT, "close"))(fd); // We are not allowed to call drmSetMaster() without CAP_SYS_ADMIN, // but since we just dropped the master, we can become master by // simply creating a new FD. Let's do it. if (__OPEN_NEEDS_MODE(flags)) { fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags, mode); } else { fd = ((typeof(open)*)dlsym(RTLD_NEXT, funcname))(pathname, flags); } } if (fd >= 0) { // Start with DRM master on the new FD drmSetMaster(fd); // Insert the FD into the table g_SdlDrmMasterFds[freeFdIndex] = fd; g_SdlDrmMasterFdCount++; } SDL_AtomicUnlock(&g_FdTableLock); } } } return fd; } #endif