Skip to content

MinGW: port POSIX socket compatibility layer #2046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 139 commits into
base: master
Choose a base branch
from

Conversation

kinkie
Copy link
Contributor

@kinkie kinkie commented Mar 29, 2025

winsock's socket-related functions are very similar but not
identical to the POSIX standard. Make the mswindows
compatibility layer available to MinGW, and modernize it.

Implement portable wrappers around socket-related calls, named
x[function], and use them in all callsites.

Error highlighting the issue:

TcpAcceptor.cc: In member function
  'bool Comm::TcpAcceptor::acceptInto(Comm::ConnectionPointer&)':
TcpAcceptor.cc:352:17: error:
  comparison of unsigned expression in '< 0'
  is always false [-Werror=type-limits]
  352 |     if (rawSock < 0) {

@kinkie

This comment was marked as resolved.

@rousskov rousskov self-requested a review March 29, 2025 14:24
yadij

This comment was marked as resolved.

@yadij yadij added the S-waiting-for-author author action is expected (and usually required) label Mar 30, 2025
@rousskov
Copy link
Contributor

more socket-related functions will need to have this treatment. I wonder whether it's best to have each have their own header and implementation or to group them all under a single xsocket.* file

We should group sys/socket.h wrappers (i.e. listen(2), accept(2), connect(2), shutdown(2), getsockopt(2), etc.), especially if they have similar implementations.

A more difficult question is whether introduction of xfoo() warrants conversion from legacy POSIX foo() to modern C++ API: If every foo() caller is a C++ caller and that caller has to be updated anyway, then it could be the right time to start doing proper memory management and error handling (instead of writing or preserving awkward and duplicated code). That requires a lot more/serious work though. It may also require to get rid of the remaining legacy C callers.

@yadij yadij dismissed their stale review April 9, 2025 13:41

okay, pending @rousskov review issues and final call on whether to use compat/xsocket.* filenames.

kinkie added 7 commits April 17, 2025 08:22
Windows' accept(2) has the same signature as
POSIX', but returns a SOCKET (under the hood, a
int *). This confuses error handling code
in squid, which uses POSIX return values
to detect error values.

Implement xaccept() to wrap around it, taking
the implementation from mswindows.h and using
it on MinGW as well.

Error highlighting the issue:
```
TcpAcceptor.cc: In member function
  'bool Comm::TcpAcceptor::acceptInto(Comm::ConnectionPointer&)':
TcpAcceptor.cc:352:17: error:
  comparison of unsigned expression in '< 0'
  is always false [-Werror=type-limits]
  352 |     if (rawSock < 0) {
```
@kinkie kinkie changed the title MinGW: implement xaccept() MinGW: port POSIX socket compatibility layer Apr 18, 2025
yadij

This comment was marked as resolved.

@kinkie kinkie requested a review from yadij May 21, 2025 08:46
@kinkie
Copy link
Contributor Author

kinkie commented May 21, 2025

@yadij I think this got to a point where I would feel confident about checkpointing and continuing in a new PR

rousskov

This comment was marked as resolved.

@@ -23,18 +23,34 @@
#define _S_IWRITE 0x0080
#endif

/// true if handle is a socket, false if a file or error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where feasible, we only document one of the mutually exclusive boolean result values to avoid ambiguities, conflicts, etc.

Suggested change
/// true if handle is a socket, false if a file or error
/// whether a given handle is a valid socket

isSocket(intptr_t handle)
{
if (!isValidSocketHandle(handle)) {
// errno is already set by _get_osfhandle()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this function guarantees errno setting on errors, then it is missing a SetErrnoFromWsaError() below, right? If it does not guarantee, then this comment is misleading.

Suggested change
// errno is already set by _get_osfhandle()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean calling SetErrnoFromWsaError(); before the return false; statement .... then yes that is missing.

Copy link
Contributor

@rousskov rousskov May 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you mean calling SetErrnoFromWsaError(); before the return false; statement

Yes, I do (and there are two return false statement below, but it can be argued that only the second one is relevant here). However, the If parts of my change request are very important -- I do not know whether this function should guarantee/set errno, and the correct way to address my change request depends on whether that guarantee exists.

In the ideal world, isFoo() test functions should not alter global state and, hence, should not promise to set errno. I hope we can agree on (and approach) that ideal and, hence, should not promise that isSocket() sets errno, but that remains to be seen.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From _get_osfhandle() documentation:

returns INVALID_HANDLE_VALUE (-1). It also sets errno to EBADF, indicating an invalid file handle.

There is no documentation about what a call to WSAGetLastError() will produce. So IMHO we should not call our mapping function and clobber that EBADF value with an unknown and possibly unrelated error value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no documentation about what a call to WSAGetLastError() will produce

IMO, existing WSAGetLastError() documentation strongly implies that WSAGetLastError() does not change errno. However, WSAGetLastError() behavior is secondary to what this change request is about.

I suggest to adopt an "isFoo() test functions should not alter global state" principle and adjust isSocket() code accordingly (i.e. apply my suggestion as is, without adding a SetErrnoFromWsaError() call).

Due to factors outside of our control (e.g., globals-changing non-Squid functions that isFoo() has to call), we cannot fully comply with that principle in some cases, but we can usually make our own code compliant.

Copy link
Contributor

@yadij yadij Jun 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree on not adding the SetErrnoFromWsaError() call. At least for the OSF API calls. If for no reason other than they are not WSA API calls.

FWIW, IMO that handling of "is" functions/methods is basic good practice we should not need to have a policy about. That calls into question whether these functions should be "is functions" at all. Maybe they are better as "requireX" instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree on not adding the SetErrnoFromWsaError() call. At least for the OSF API calls. If for no reason other than they are not WSA API calls.

FWIW, IMO that handling of "is" functions/methods is basic good practice we should not need to have a policy about.

Glad we agree regarding the important bits. The difference between an implicit "basic good practice" (that everybody applies) and an explicit principle (that everybody honors) is a secondary detail. Current PR code does not follow that practice or honors that principle, prompting this change request.

That calls into question whether these functions should be "is functions" at all. Maybe they are better as "requireX" instead.

  • If callers need a boolean function that tests whether condition Foo is true, then isFoo() is better than requireFoo().
  • If callers need a void function that enforces condition Bar, then requireBar() is better than isBar().
  • If callers need a boolean function that helps the caller enforce condition Baz (i.e. a function that contains partial enforcement code in addition to condition testing code), then neither isBaz() nor requireBaz() work well.

AFAICT, all existing isSocket() callers belong to the first category and do not enforce an "x is a socket" condition, so isSocket() name sounds correct to me.

@rousskov rousskov added the S-waiting-for-author author action is expected (and usually required) label May 22, 2025
yadij and others added 3 commits June 15, 2025 22:49
@yadij yadij removed the S-waiting-for-reviewer ready for review: Set this when requesting a (re)review using GitHub PR Reviewers box label Jun 15, 2025
{ WSAEISCONN, EISCONN },

{ WSAEWOULDBLOCK, EWOULDBLOCK },
// Windows does not have a equivalent of EAGAIN
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To match the other two similar comments (and fix grammar):

Suggested change
// Windows does not have a equivalent of EAGAIN
// no Windows error code maps to EAGAIN

// no Windows error code maps to ENFILE

{ WSAECONNRESET, ECONNRESET }
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend sorting {WSAX, EY} entries alphabetically so that it is easier to find a particular error and easier to decide where to place new entries. I would sort by the second/EY field (so that comments can participate in the same sort rather than moved to the beginning or end), but sorting by the first/WSAX field is better than no sorting at all.

I do not insist on this, but the current grouping looks odd. I am aware of this grouping origins.

const auto result = ::getsockopt(handle, level, option_name, static_cast<char *>(option_value), &ol);
if (result == SOCKET_ERROR)
SetErrnoFromWsaError();
return result;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unresolving: xrecvfrom() is still broken.

// errno is already set by _get_osfhandle()
return SOCKET_ERROR;
}
auto fl = static_cast<int>(*fromlen);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WIndows and Linux documentation says that fromlen may be nil. Please do not dereference without checking.

While fixing this, please rename fromlen to fromLen or similar to use camelCase and make this variable more readable.

inline ssize_t
xsendto(int socketFd, const void * buf, size_t len, int flags, const struct sockaddr * to, socklen_t l)
{
return sendto(socketFd, static_cast<const char *>(buf), static_cast<int>(len), flags, to, l);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two problematic casts are Windows-specific and do not belong to this non-WIndows code, right? At least some of the existing callers do not perform these cast. Please check all non-Windows wrappers for similar unwanted casts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-for-author author action is expected (and usually required)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants