fix(posix): handle Windows shim edge cases

PX4 command clients need poll() to work for more than Winsock sockets on Windows because daemon output is relayed through CRT pipes. Classify descriptors before dispatching to WSAPoll, handle pipe and console readiness locally, and keep socket-only calls on the fast path.

Also avoid duplicate Windows socket control-message declarations, provide common stat predicates, clean up adapter-name allocation failures, and remove the uppercase Windows.h shim now that the Windows include path no longer needs it.

Signed-off-by: Nuno Marques <n.marques21@hotmail.com>
This commit is contained in:
Nuno Marques
2026-05-05 20:59:09 -07:00
parent 15de3a6337
commit 1423556b42
6 changed files with 410 additions and 56 deletions
@@ -1,43 +0,0 @@
/****************************************************************************
*
* Copyright (C) 2026 PX4 Development Team. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* 3. Neither the name PX4 nor the names of its contributors may be
* used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************/
/**
* @file Windows.h
*
* MinGW-w64 only ships the lowercase <windows.h>, but a lot of
* Windows-targeted code (including Micro-XRCE-DDS-Client) writes
* #include <Windows.h>. On case-sensitive Linux filesystems that fails
* when cross-compiling. This header is on the include search path
* ahead of the MinGW sysroot and just forwards to the real header.
*/
#pragma once
#include <windows.h>
+363 -9
View File
@@ -33,14 +33,22 @@
/**
* @file poll.h
*
* Maps POSIX poll() onto Win32 WSAPoll(). WSAPoll has the same struct
* layout and event flags as POSIX poll; it has been available since
* Vista but only works on sockets, which is exactly how PX4 uses it
* (px4_daemon socket pair, muorb sockets, mavlink).
* Maps POSIX poll() onto Win32 readiness primitives. WSAPoll has the
* same struct layout and event flags as POSIX poll; it has been available
* since Vista but only works on sockets. PX4 also routes command output
* through CRT pipes on Windows, so pipe-backed fds are handled locally and
* socket-only calls keep the direct WSAPoll fast path.
*/
#pragma once
#include <errno.h>
#include <io.h>
#include <limits.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <winsock2.h>
#include <windows.h>
#ifdef __cplusplus
extern "C" {
@@ -68,18 +76,364 @@ extern "C" {
#define POLLREMOVE 0x1000
#endif
typedef struct pollfd POLLFD; /* alias for readability */
typedef unsigned long nfds_t;
enum px4_windows_poll_fd_kind {
PX4_WINDOWS_POLL_FD_IGNORED = 0,
PX4_WINDOWS_POLL_FD_SOCKET,
PX4_WINDOWS_POLL_FD_PIPE,
PX4_WINDOWS_POLL_FD_CHAR,
PX4_WINDOWS_POLL_FD_DISK,
PX4_WINDOWS_POLL_FD_INVALID
};
static inline int px4_windows_poll_fd_ignored(SOCKET fd)
{
return (intptr_t)fd < 0;
}
static inline int px4_windows_poll_errno_from_wsa(int wsa_error)
{
switch (wsa_error) {
case WSAEINTR:
return EINTR;
case WSAEINVAL:
return EINVAL;
case WSAENOBUFS:
return ENOMEM;
default:
return EBADF;
}
}
static inline enum px4_windows_poll_fd_kind px4_windows_poll_classify_fd(SOCKET fd, HANDLE *handle, DWORD *file_type)
{
int socket_type = 0;
int socket_type_len = sizeof(socket_type);
if (px4_windows_poll_fd_ignored(fd)) {
if (handle != NULL) {
*handle = INVALID_HANDLE_VALUE;
}
if (file_type != NULL) {
*file_type = FILE_TYPE_UNKNOWN;
}
return PX4_WINDOWS_POLL_FD_IGNORED;
}
/* Winsock SOCKET values and CRT fd numbers live in different namespaces.
* Test for a real socket first so a small SOCKET value is never mistaken
* for a same-numbered CRT fd. */
if (getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&socket_type, &socket_type_len) == 0) {
if (handle != NULL) {
*handle = INVALID_HANDLE_VALUE;
}
if (file_type != NULL) {
*file_type = FILE_TYPE_UNKNOWN;
}
return PX4_WINDOWS_POLL_FD_SOCKET;
}
if (fd > (SOCKET)INT_MAX) {
if (handle != NULL) {
*handle = INVALID_HANDLE_VALUE;
}
if (file_type != NULL) {
*file_type = FILE_TYPE_UNKNOWN;
}
return PX4_WINDOWS_POLL_FD_INVALID;
}
HANDLE h = (HANDLE)_get_osfhandle((int)fd);
if (h == INVALID_HANDLE_VALUE || h == NULL) {
if (handle != NULL) {
*handle = INVALID_HANDLE_VALUE;
}
if (file_type != NULL) {
*file_type = FILE_TYPE_UNKNOWN;
}
return PX4_WINDOWS_POLL_FD_INVALID;
}
SetLastError(ERROR_SUCCESS);
DWORD type = GetFileType(h);
DWORD error = GetLastError();
if (type == FILE_TYPE_UNKNOWN && error != ERROR_SUCCESS) {
if (handle != NULL) {
*handle = INVALID_HANDLE_VALUE;
}
if (file_type != NULL) {
*file_type = FILE_TYPE_UNKNOWN;
}
return PX4_WINDOWS_POLL_FD_INVALID;
}
if (handle != NULL) {
*handle = h;
}
if (file_type != NULL) {
*file_type = type;
}
switch (type) {
case FILE_TYPE_PIPE:
return PX4_WINDOWS_POLL_FD_PIPE;
case FILE_TYPE_CHAR:
return PX4_WINDOWS_POLL_FD_CHAR;
case FILE_TYPE_DISK:
return PX4_WINDOWS_POLL_FD_DISK;
default:
return PX4_WINDOWS_POLL_FD_INVALID;
}
}
static inline short px4_windows_poll_ready_events(short requested_events, short ready_events)
{
return (short)(requested_events & ready_events);
}
static inline short px4_windows_poll_pipe_revents(HANDLE handle, short events)
{
const short read_events = (short)(POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI);
const short write_events = (short)(POLLOUT | POLLWRNORM | POLLWRBAND);
short revents = 0;
DWORD available = 0;
if (PeekNamedPipe(handle, NULL, 0, NULL, &available, NULL)) {
if (available > 0) {
revents |= px4_windows_poll_ready_events(events, read_events);
}
} else {
const DWORD error = GetLastError();
if (error == ERROR_BROKEN_PIPE || error == ERROR_HANDLE_EOF || error == ERROR_PIPE_NOT_CONNECTED) {
revents |= POLLHUP;
if ((events & read_events) != 0) {
revents |= POLLIN;
}
} else {
revents |= POLLERR;
}
}
/* Anonymous pipes are used for PX4 command-output relay. Treat write
* interest as immediately ready, matching the previous shim behavior and
* avoiding WSAPoll on non-socket CRT fds. */
revents |= px4_windows_poll_ready_events(events, write_events);
return revents;
}
static inline short px4_windows_poll_char_revents(HANDLE handle, short events)
{
const short read_events = (short)(POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI);
const short write_events = (short)(POLLOUT | POLLWRNORM | POLLWRBAND);
short revents = 0;
DWORD console_mode = 0;
if ((events & read_events) != 0) {
if (GetConsoleMode(handle, &console_mode)) {
DWORD pending = 0;
if (GetNumberOfConsoleInputEvents(handle, &pending) && pending > 0) {
revents |= px4_windows_poll_ready_events(events, read_events);
}
} else {
const DWORD wait_result = WaitForSingleObject(handle, 0);
if (wait_result == WAIT_OBJECT_0) {
revents |= px4_windows_poll_ready_events(events, read_events);
} else if (wait_result == WAIT_FAILED) {
revents |= POLLERR;
}
}
}
revents |= px4_windows_poll_ready_events(events, write_events);
return revents;
}
static inline short px4_windows_poll_disk_revents(short events)
{
const short ready_events = (short)(POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI | POLLOUT | POLLWRNORM | POLLWRBAND);
return px4_windows_poll_ready_events(events, ready_events);
}
/**
* @brief Poll socket descriptors using WSAPoll().
* @brief Poll socket descriptors and Windows CRT pipe descriptors.
*
* @return Number of ready descriptors, 0 on timeout, or SOCKET_ERROR.
* @return Number of ready descriptors, 0 on timeout, or -1 with errno set.
*/
static inline int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
return WSAPoll(fds, (ULONG)nfds, timeout);
if (timeout < -1) {
errno = EINVAL;
return -1;
}
if (fds == NULL && nfds > 0) {
errno = EFAULT;
return -1;
}
if (nfds == 0) {
if (timeout < 0) {
Sleep(INFINITE);
} else if (timeout > 0) {
Sleep((DWORD)timeout);
}
return 0;
}
int socket_only = true;
int has_ignored_fd = false;
for (nfds_t i = 0; i < nfds; ++i) {
if (px4_windows_poll_fd_ignored((SOCKET)fds[i].fd)) {
has_ignored_fd = true;
continue;
}
if (px4_windows_poll_classify_fd((SOCKET)fds[i].fd, NULL, NULL) != PX4_WINDOWS_POLL_FD_SOCKET) {
socket_only = false;
break;
}
}
if (socket_only && !has_ignored_fd) {
const int ret = WSAPoll(fds, (ULONG)nfds, timeout);
if (ret == SOCKET_ERROR) {
errno = px4_windows_poll_errno_from_wsa(WSAGetLastError());
return -1;
}
return ret;
}
struct pollfd *socket_fds = (struct pollfd *)malloc(sizeof(struct pollfd) * nfds);
nfds_t *socket_indices = (nfds_t *)malloc(sizeof(nfds_t) * nfds);
if (socket_fds == NULL || socket_indices == NULL) {
free(socket_fds);
free(socket_indices);
errno = ENOMEM;
return -1;
}
const ULONGLONG start_ms = GetTickCount64();
for (;;) {
int ready_count = 0;
nfds_t socket_count = 0;
for (nfds_t i = 0; i < nfds; ++i) {
fds[i].revents = 0;
if (px4_windows_poll_fd_ignored((SOCKET)fds[i].fd)) {
continue;
}
HANDLE h = INVALID_HANDLE_VALUE;
DWORD file_type = FILE_TYPE_UNKNOWN;
const enum px4_windows_poll_fd_kind kind = px4_windows_poll_classify_fd((SOCKET)fds[i].fd, &h, &file_type);
if (kind == PX4_WINDOWS_POLL_FD_PIPE) {
fds[i].revents = px4_windows_poll_pipe_revents(h, fds[i].events);
if (fds[i].revents != 0) {
++ready_count;
}
continue;
}
if (kind == PX4_WINDOWS_POLL_FD_SOCKET) {
socket_fds[socket_count] = fds[i];
socket_indices[socket_count] = i;
++socket_count;
continue;
}
if (kind == PX4_WINDOWS_POLL_FD_CHAR) {
fds[i].revents = px4_windows_poll_char_revents(h, fds[i].events);
} else if (kind == PX4_WINDOWS_POLL_FD_DISK) {
(void)file_type;
fds[i].revents = px4_windows_poll_disk_revents(fds[i].events);
} else if (kind == PX4_WINDOWS_POLL_FD_INVALID) {
fds[i].revents = POLLNVAL;
}
if (fds[i].revents != 0) {
++ready_count;
}
}
if (socket_count > 0) {
const int socket_ready = WSAPoll(socket_fds, (ULONG)socket_count, 0);
if (socket_ready == SOCKET_ERROR) {
free(socket_fds);
free(socket_indices);
errno = px4_windows_poll_errno_from_wsa(WSAGetLastError());
return -1;
}
for (nfds_t i = 0; i < socket_count; ++i) {
if (socket_fds[i].revents != 0) {
fds[socket_indices[i]].revents = socket_fds[i].revents;
}
}
ready_count += socket_ready;
}
if (ready_count > 0 || timeout == 0) {
free(socket_fds);
free(socket_indices);
return ready_count;
}
if (timeout > 0) {
const ULONGLONG elapsed_ms = GetTickCount64() - start_ms;
if (elapsed_ms >= (ULONGLONG)timeout) {
free(socket_fds);
free(socket_indices);
return 0;
}
}
Sleep(1);
}
}
#ifdef __cplusplus
@@ -47,7 +47,15 @@
#include <sys/types.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#ifndef OPTIONAL
#define _PX4_WINDOWS_SHIM_DEFINED_OPTIONAL
#define OPTIONAL
#endif
#include <mswsock.h>
#ifdef _PX4_WINDOWS_SHIM_DEFINED_OPTIONAL
#undef OPTIONAL
#undef _PX4_WINDOWS_SHIM_DEFINED_OPTIONAL
#endif
#ifdef __cplusplus
extern "C" {
@@ -98,12 +106,20 @@ struct msghdr {
int msg_flags;
};
/** @brief Ancillary data header compatible with POSIX CMSG_* helpers. */
/** @brief Ancillary data header compatible with POSIX CMSG_* helpers.
*
* MinGW's <mswsock.h> already provides `struct cmsghdr` (via `_WSACMSGHDR`)
* together with the CMSG_* helpers when targeting Vista+, which is always
* our case (_WIN32_WINNT >= 0x0A00). Use the same sentinel as the macro
* guards below to avoid a redefinition under MinGW while still providing
* the struct on platforms that omit it. */
#ifndef CMSG_FIRSTHDR
struct cmsghdr {
size_t cmsg_len;
int cmsg_level;
int cmsg_type;
};
#endif
#ifndef _PX4_UCRED_DEFINED
#define _PX4_UCRED_DEFINED
@@ -228,8 +244,10 @@ int socketpair(int domain, int type, int protocol, int socket_vector[2]);
* @name MSVC socket wrappers
*
* MSVC does not expose POSIX-like int socket descriptors. These wrappers keep
* PX4 source code using socket(), bind(), send(), etc. while centralizing
* Winsock startup and errno translation in the Windows backend.
* C source code using socket(), bind(), send(), etc. while centralizing
* Winsock errno translation in the Windows backend. C++ has many methods named
* send(), connect(), and shutdown(), so C++ call sites use the declarations
* directly when they need an explicit wrapper.
*
* @{
*/
@@ -247,6 +265,7 @@ int WSAAPI px4_windows_sendto(SOCKET s, const char *buf, int len, int flags,
char *px4_windows_strerror(int e);
/** @} */
#ifndef __cplusplus
#define socket(...) px4_windows_socket(__VA_ARGS__)
#define bind(...) px4_windows_bind(__VA_ARGS__)
#define listen(...) px4_windows_listen(__VA_ARGS__)
@@ -259,6 +278,7 @@ char *px4_windows_strerror(int e);
#define recvfrom(...) px4_windows_recvfrom(__VA_ARGS__)
#define sendto(...) px4_windows_sendto(__VA_ARGS__)
#define strerror(...) px4_windows_strerror(__VA_ARGS__)
#endif /* !__cplusplus */
#endif
#ifdef __cplusplus
@@ -154,6 +154,12 @@ static inline int px4_mkdir_shim(const char *path, mode_t mode)
#ifndef S_ISLNK
#define S_ISLNK(m) (0)
#endif
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif
#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif
#ifndef S_ISFIFO
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
#endif
@@ -86,6 +86,17 @@ extern "C" struct if_nameindex *if_nameindex(void)
WideCharToMultiByte(CP_UTF8, 0, a->FriendlyName, -1, name, IF_NAMESIZE, nullptr, nullptr);
result[i].if_index = a->IfIndex;
result[i].if_name = _strdup(name);
/* if_freenameindex() uses a NULL if_name as the array sentinel and stops
* walking. A silent _strdup() failure mid-loop would truncate the iteration
* and leak the remaining names; bail out instead so the caller sees ENOMEM. */
if (!result[i].if_name) {
for (unsigned int j = 0; j < i; j++) { free(result[j].if_name); }
free(result);
free(adapters);
errno = ENOMEM;
return nullptr;
}
}
free(adapters);
return result;
@@ -108,7 +108,13 @@ static int align_buf(char **cursor, size_t *remaining, size_t alignment)
static struct passwd *fill_passwd()
{
DWORD n = sizeof(_pw_name);
if (!GetUserNameA(_pw_name, &n)) { strcpy(_pw_name, "px4"); }
if (!GetUserNameA(_pw_name, &n)) {
/* GetUserNameA failed; fall back to a stable placeholder. Use a bounded
* copy rather than strcpy() so MSVC does not flag the deprecated CRT
* API and we cannot accidentally overflow if _pw_name shrinks later. */
strncpy(_pw_name, "px4", sizeof(_pw_name) - 1);
_pw_name[sizeof(_pw_name) - 1] = '\0';
}
const char *home = getenv("USERPROFILE");
if (!home) { home = "C:\\"; }
strncpy(_pw_dir, home, sizeof(_pw_dir) - 1);