diff --git a/src/platform/linux/kmsgrab.cpp b/src/platform/linux/kmsgrab.cpp index 909b84a06a0..311b08e6572 100644 --- a/src/platform/linux/kmsgrab.cpp +++ b/src/platform/linux/kmsgrab.cpp @@ -86,6 +86,86 @@ namespace platf { * @brief Set up privileged worker thread exclusively for handling DRM capture resources. */ class privileged_drm_worker { + private: + static privileged_drm_worker &instance() { + static privileged_drm_worker w; + return w; + } + + void drop_privileges() { + instance().run([] { + platf::drop_elevated_privileges(true); + }); + } + + privileged_drm_worker(): + thread_ {[this] { + sigset_t all; + sigfillset(&all); + if (pthread_sigmask(SIG_BLOCK, &all, nullptr) != 0) { + BOOST_LOG(error) << "Failed to block signals in drm_worker"sv; + queue_.stop(); + return; + } + + platf::set_thread_name("drm_worker"); + for (;;) { + auto task = queue_.pop(); + if (!task) { + break; + } + (*task)(); + } + }} { + } + + ~privileged_drm_worker() { + queue_.stop(); + } + + /** + * @brief Run a function via the privileged worker and capture its errno value. + * + * @param f Function to execute. + * @return Pair containing the function result and worker thread errno value. + */ + template + static auto run_with_errno(F &&f) { + return instance().run([f = std::forward(f)]() mutable { + errno = 0; + const auto result = f(); + const auto errno_value = errno; + return std::pair {result, errno_value}; + }); + } + + template + auto run(F &&f, const std::source_location &loc = std::source_location::current()) -> std::invoke_result_t { + using R = std::invoke_result_t; + auto task = std::make_shared>( + [f = std::forward(f)]() mutable -> R { + #if !defined(__FreeBSD__) + cap_sys_admin admin; + #endif + return f(); + } + ); + auto fut = task->get_future(); + + if (!queue_.raise([task]() mutable { + (*task)(); + })) { + throw privileged_drm_worker_stopped { + "privileged_drm_worker: task rejected in "s + loc.function_name() + " (worker stopping)"s + }; + } + + return fut.get(); + } + + safe::queue_t> queue_ {32, safe::queue_t>::overflow_policy_e::reject}; + std::jthread thread_; + public: static void ensure_started() { instance(); @@ -95,98 +175,229 @@ namespace platf { instance().drop_privileges(); } - // deliberately align prototype to match path signature via init(const char *path) + /** + * @brief Open a DRM card file descriptor in the privileged worker thread. + * Note: prototype aligned to match path signature via init(const char *path). + * + * @param path Path to the DRM device node. + * @return File descriptor on success, or -1 on failure. + */ static int open_drm_card_fd_privileged(const char *path) { try { - return instance().run([path] { + const auto [result, errno_value] = run_with_errno([path] { return platf::open_drm_card_fd(path); }); - } catch (const privileged_drm_worker_stopped &) { + + if (!result) { + errno = errno_value; + } + + return result; + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); return -1; } } + /** + * @brief Wrap drmIoctl call in privileged worker thread. + * + * @param fd File descriptor + * @param request Request data. + * @param arg Argument + * @return Standard drmIoctl return codes passthrough when wrapped or -1 when thrown. + */ + template + static int drmIoctl_privileged(int fd, unsigned long request, T *arg) { + try { + const auto [result, errno_value] = run_with_errno([fd, request, arg] { + return drmIoctl(fd, request, arg); + }); + + if (result < 0) { + errno = errno_value; + } + + return result; + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); + return -1; + } + } + + /** + * @brief Wrap drmModeGetConnector call in privileged worker thread. + * + * @param fd DRM file descriptor. + * @param connectorId DRM connector id. + * @return Pointer to drmModeConnector structure on success, or nullptr on failure. + */ + static drmModeConnectorPtr drmModeGetConnector_privileged(int fd, uint32_t connectorId) { + try { + const auto [result, errno_value] = run_with_errno([fd, connectorId] { + return drmModeGetConnector(fd, connectorId); + }); + + if (!result) { + errno = errno_value; + } + + return result; + + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); + return nullptr; + } + } + + /** + * @brief Wrap drmModeGetFB2 call in privileged worker thread. + * + * @param fd DRM file descriptor. + * @param bufferId Framebuffer ID. + * @return Pointer to drmModeFB2 structure on success, or nullptr on failure. + */ static drmModeFB2Ptr drmModeGetFB2_privileged(int fd, uint32_t bufferId) { try { - return instance().run([fd, bufferId] { + const auto [result, errno_value] = run_with_errno([fd, bufferId] { return drmModeGetFB2(fd, bufferId); }); - } catch (const privileged_drm_worker_stopped &) { + + if (!result) { + errno = errno_value; + } + + return result; + + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); return nullptr; } } + /** + * @brief Wrap drmModeGetFB call in privileged worker thread. + * + * @param fd DRM file descriptor. + * @param bufferId Framebuffer ID. + * @return Pointer to drmModeFB structure on success, or nullptr on failure. + */ static drmModeFBPtr drmModeGetFB_privileged(int fd, uint32_t bufferId) { try { - return instance().run([fd, bufferId] { + const auto [result, errno_value] = run_with_errno([fd, bufferId] { return drmModeGetFB(fd, bufferId); }); - } catch (const privileged_drm_worker_stopped &) { + + if (!result) { + errno = errno_value; + } + + return result; + + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); return nullptr; } } - private: - static privileged_drm_worker &instance() { - static privileged_drm_worker w; - return w; - } + /** + * @brief Wrap drmModeGetPlane call in privileged worker thread. + * + * @param fd DRM file descriptor. + * @param plane_id DRM plane id. + * @return Pointer to drmModePlane structure on success, or nullptr on failure. + */ + static drmModePlanePtr drmModeGetPlane_privileged(int fd, uint32_t plane_id) { + try { + const auto [result, errno_value] = run_with_errno([fd, plane_id] { + return drmModeGetPlane(fd, plane_id); + }); - void drop_privileges() { - instance().run([] { - platf::drop_elevated_privileges(true); - }); - } + if (!result) { + errno = errno_value; + } - privileged_drm_worker(): - thread_ {[this] { - sigset_t all; - sigfillset(&all); - if (pthread_sigmask(SIG_BLOCK, &all, nullptr) != 0) { - BOOST_LOG(error) << "Failed to block signals in drm_worker"sv; - queue_.stop(); - return; - } + return result; - platf::set_thread_name("drm_worker"); - for (;;) { - auto task = queue_.pop(); - if (!task) { - break; - } - (*task)(); - } - }} { + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); + return nullptr; + } } - ~privileged_drm_worker() { - queue_.stop(); - } + /** + * @brief Wrap drmModeGetPlaneResources call in privileged worker thread. + * + * @param fd DRM file descriptor. + * @return Pointer to drmModePlaneRes structure on success, or nullptr on failure. + */ + static drmModePlaneResPtr drmModeGetPlaneResources_privileged(int fd) { + try { + const auto [result, errno_value] = run_with_errno([fd] { + return drmModeGetPlaneResources(fd); + }); - template - auto run(F &&f) -> std::invoke_result_t { - using R = std::invoke_result_t; - auto task = std::make_shared>( - [f = std::forward(f)]() mutable -> R { -#if !defined(__FreeBSD__) - cap_sys_admin admin; -#endif - return f(); + if (!result) { + errno = errno_value; } - ); - auto fut = task->get_future(); - if (!queue_.raise([task]() mutable { - (*task)(); - })) { - throw privileged_drm_worker_stopped {"privileged_drm_worker: task rejected (worker stopping)"}; + return result; + + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); + return nullptr; } + } - return fut.get(); + /** + * @brief Wrap drmModeGetResources call in privileged worker thread. + * + * @param fd DRM file descriptor. + * @return Pointer to drmModeRes structure on success, or nullptr on failure. + */ + static drmModeResPtr drmModeGetResources_privileged(int fd) { + try { + const auto [result, errno_value] = run_with_errno([fd] { + return drmModeGetResources(fd); + }); + + if (!result) { + errno = errno_value; + } + + return result; + + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); + return nullptr; + } } - safe::queue_t> queue_ {32, safe::queue_t>::overflow_policy_e::reject}; - std::jthread thread_; + /** + * @brief Wrap drmSetClientCap call in privileged worker thread. + * + * @param fd DRM file descriptor. + * @param capability Capability to set. + * @param capability Value to set. + * @return 1 on success, -1 on failure. + */ + static int drmSetClientCap_privileged(int fd, uint64_t capability, uint64_t value) { + try { + const auto [result, errno_value] = run_with_errno([fd, capability, value] { + return drmSetClientCap(fd, capability, value); + }); + + if (!result) { + errno = errno_value; + } + + return result; + + } catch (const privileged_drm_worker_stopped &err) { + BOOST_LOG(error) << err.what(); + return -1; + } + } }; } // namespace @@ -266,19 +477,30 @@ namespace platf { } ~wrapper_fb() { - std::ranges::for_each(handles, [&](auto &handle) { - if (handle) { - struct drm_gem_close close_args = {}; - close_args.handle = handle; + try { + std::ranges::for_each(handles, [&](auto &handle) { + if (handle) { + struct drm_gem_close close_args = {}; + close_args.handle = handle; + + platf::kms::privileged_drm_worker::drmIoctl_privileged( + card_fd, + DRM_IOCTL_GEM_CLOSE, + &close_args + ); + } + }); - drmIoctl(card_fd, DRM_IOCTL_GEM_CLOSE, &close_args); + if (fb) { + drmModeFreeFB(fb); + } else if (fb2) { + drmModeFreeFB2(fb2); } - }); - if (fb) { - drmModeFreeFB(fb); - } else if (fb2) { - drmModeFreeFB2(fb2); + } catch (const std::exception &err) { + BOOST_LOG(error) << "Exception during DRM framebuffer cleanup: "sv << err.what(); + } catch (...) { + BOOST_LOG(error) << "Exception during DRM framebuffer cleanup: unknown exception"sv; } } @@ -508,7 +730,7 @@ namespace platf { this->plane.reset(); for (; plane_p != end; ++plane_p) { - plane_t plane = drmModeGetPlane(fd, *plane_p); + plane_t plane = platf::kms::privileged_drm_worker::drmModeGetPlane_privileged(fd, *plane_p); if (!plane) { BOOST_LOG(error) << "Couldn't get drm plane ["sv << (end - plane_p) << "]: "sv << strerror(errno); continue; @@ -617,12 +839,12 @@ namespace platf { render_fd.el = dup(fd.el); } - if (drmSetClientCap(fd.el, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + if (platf::kms::privileged_drm_worker::drmSetClientCap_privileged(fd.el, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { BOOST_LOG(error) << "GPU driver doesn't support universal planes: "sv << path; return -1; } - if (drmSetClientCap(fd.el, DRM_CLIENT_CAP_ATOMIC, 1)) { + if (platf::kms::privileged_drm_worker::drmSetClientCap_privileged(fd.el, DRM_CLIENT_CAP_ATOMIC, 1)) { BOOST_LOG(warning) << "GPU driver doesn't support atomic mode-setting: "sv << path; #if defined(SUNSHINE_BUILD_X11) // We won't be able to capture the mouse cursor with KMS on non-atomic drivers, @@ -635,7 +857,7 @@ namespace platf { BOOST_LOG(warning) << "Cursor capture may fail without atomic mode-setting support!"sv; } - plane_res.reset(drmModeGetPlaneResources(fd.el)); + plane_res.reset(platf::kms::privileged_drm_worker::drmModeGetPlaneResources_privileged(fd.el)); if (!plane_res) { BOOST_LOG(error) << "Couldn't get drm plane resources"sv; return -1; @@ -690,7 +912,7 @@ namespace platf { * @return DRM card resource list. */ res_t res() { - return drmModeGetResources(fd.el); + return platf::kms::privileged_drm_worker::drmModeGetResources_privileged(fd.el); } /** @@ -780,7 +1002,7 @@ namespace platf { * @return Owning pointer to the DRM connector object. */ connector_interal_t connector(std::uint32_t id) { - return drmModeGetConnector(fd.el, id); + return platf::kms::privileged_drm_worker::drmModeGetConnector_privileged(fd.el, id); } /** @@ -900,7 +1122,7 @@ namespace platf { * @return Plane metadata for the requested DRM plane. */ plane_t operator[](std::uint32_t index) { - return drmModeGetPlane(fd.el, plane_res->planes[index]); + return platf::kms::privileged_drm_worker::drmModeGetPlane_privileged(fd.el, plane_res->planes[index]); } /** @@ -1351,7 +1573,7 @@ namespace platf { return; } - plane_t plane = drmModeGetPlane(card.fd.el, cursor_plane_id); + plane_t plane = platf::kms::privileged_drm_worker::drmModeGetPlane_privileged(card.fd.el, cursor_plane_id); std::optional prop_crtc_x; std::optional prop_crtc_y; @@ -1473,7 +1695,7 @@ namespace platf { if (mapped_data == MAP_FAILED && errno == ENOSYS) { drm_mode_map_dumb map = {}; map.handle = fb->handles[0]; - if (drmIoctl(card.fd.el, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) { + if (platf::kms::privileged_drm_worker::drmIoctl_privileged(card.fd.el, DRM_IOCTL_MODE_MAP_DUMB, &map) < 0) { BOOST_LOG(error) << "Failed to map cursor FB as dumb buffer: "sv << strerror(errno); captured_cursor.visible = false; return; @@ -1493,7 +1715,7 @@ namespace platf { // Prepare to read the dmabuf from the CPU struct dma_buf_sync sync; sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ; - drmIoctl(plane_fd.el, DMA_BUF_IOCTL_SYNC, &sync); + platf::kms::privileged_drm_worker::drmIoctl_privileged(plane_fd.el, DMA_BUF_IOCTL_SYNC, &sync); // If the image is tightly packed, copy it in one shot if (fb->pitches[0] == src_w * 4 && src_x == 0) { @@ -1508,7 +1730,7 @@ namespace platf { // End the CPU read and unmap the dmabuf sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ; - drmIoctl(plane_fd.el, DMA_BUF_IOCTL_SYNC, &sync); + platf::kms::privileged_drm_worker::drmIoctl_privileged(plane_fd.el, DMA_BUF_IOCTL_SYNC, &sync); munmap(mapped_data, mapped_size); @@ -1542,7 +1764,7 @@ namespace platf { } } - plane_t plane = drmModeGetPlane(card.fd.el, plane_id); + plane_t plane = platf::kms::privileged_drm_worker::drmModeGetPlane_privileged(card.fd.el, plane_id); frame_timestamp = std::chrono::steady_clock::now(); auto fb = card.fb(plane.get());