mirror of
https://github.com/curl/curl.git
synced 2025-09-07 21:05:09 +03:00
curl: implement non-blocking STDIN read on Windows
Implements a seperate read thread for STDIN on Windows when curl is run with -T/--upload-file . This uses a similar technique to the nmap/ncat project, spawning a seperate thread which creates a loop-back bound socket, sending STDIN into this socket, and reading from the other end of said TCP socket in a non-blocking way in the rest of curl. Fixes #17451 Closes #17572
This commit is contained in:
parent
84b62696d9
commit
9a2663322c
|
@ -87,15 +87,39 @@ size_t tool_read_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = read(per->infd, buffer, sz*nmemb);
|
/* If we are on Windows, and using `-T .`, then per->infd points to a socket
|
||||||
if(rc < 0) {
|
connected to stdin via a reader thread, and needs to be read with recv()
|
||||||
if(errno == EAGAIN) {
|
Make sure we are in non-blocking mode and infd is not regular stdin
|
||||||
CURL_SETERRNO(0);
|
On Linux per->infd should be stdin (0) and the block below should not
|
||||||
config->readbusy = TRUE;
|
execute */
|
||||||
return CURL_READFUNC_PAUSE;
|
if(!strcmp(per->uploadfile, ".") && per->infd > 0) {
|
||||||
|
#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
|
||||||
|
rc = recv(per->infd, buffer, curlx_uztosi(sz * nmemb), 0);
|
||||||
|
if(rc < 0) {
|
||||||
|
if(SOCKERRNO == SOCKEWOULDBLOCK) {
|
||||||
|
CURL_SETERRNO(0);
|
||||||
|
config->readbusy = TRUE;
|
||||||
|
return CURL_READFUNC_PAUSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
warnf(per->config->global, "per->infd != 0: FD == %d. This behavior"
|
||||||
|
" is only supported on desktop Windows", per->infd);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rc = read(per->infd, buffer, sz*nmemb);
|
||||||
|
if(rc < 0) {
|
||||||
|
if(errno == EAGAIN) {
|
||||||
|
CURL_SETERRNO(0);
|
||||||
|
config->readbusy = TRUE;
|
||||||
|
return CURL_READFUNC_PAUSE;
|
||||||
|
}
|
||||||
|
/* since size_t is unsigned we cannot return negative values fine */
|
||||||
|
rc = 0;
|
||||||
}
|
}
|
||||||
/* since size_t is unsigned we cannot return negative values fine */
|
|
||||||
rc = 0;
|
|
||||||
}
|
}
|
||||||
if((per->uploadfilesize != -1) &&
|
if((per->uploadfilesize != -1) &&
|
||||||
(per->uploadedsofar + rc > per->uploadfilesize)) {
|
(per->uploadedsofar + rc > per->uploadfilesize)) {
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
|
|
||||||
#include "tool_bname.h"
|
#include "tool_bname.h"
|
||||||
#include "tool_doswin.h"
|
#include "tool_doswin.h"
|
||||||
|
#include "tool_msgs.h"
|
||||||
|
|
||||||
#include <curlx.h>
|
#include <curlx.h>
|
||||||
#include <memdebug.h> /* keep this as LAST include */
|
#include <memdebug.h> /* keep this as LAST include */
|
||||||
|
@ -741,6 +742,219 @@ CURLcode win32_init(void)
|
||||||
return CURLE_OK;
|
return CURLE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
|
||||||
|
/* The following STDIN non - blocking read techniques are heavily inspired
|
||||||
|
by nmap and ncat (https://nmap.org/ncat/) */
|
||||||
|
struct win_thread_data {
|
||||||
|
/* This is a copy of the true stdin file handle before any redirection. It is
|
||||||
|
read by the thread. */
|
||||||
|
HANDLE stdin_handle;
|
||||||
|
/* This is the listen socket for the thread. It is closed after the first
|
||||||
|
connection. */
|
||||||
|
curl_socket_t socket_l;
|
||||||
|
/* This is the global config - used for printing errors and so forth */
|
||||||
|
struct GlobalConfig *global;
|
||||||
|
};
|
||||||
|
|
||||||
|
static DWORD WINAPI win_stdin_thread_func(void *thread_data)
|
||||||
|
{
|
||||||
|
struct win_thread_data *tdata = (struct win_thread_data *)thread_data;
|
||||||
|
DWORD n;
|
||||||
|
int nwritten;
|
||||||
|
char buffer[BUFSIZ];
|
||||||
|
BOOL r;
|
||||||
|
|
||||||
|
SOCKADDR_IN clientAddr;
|
||||||
|
int clientAddrLen = sizeof(clientAddr);
|
||||||
|
|
||||||
|
curl_socket_t socket_w = accept(tdata->socket_l, (SOCKADDR*)&clientAddr,
|
||||||
|
&clientAddrLen);
|
||||||
|
|
||||||
|
if(socket_w == CURL_SOCKET_BAD) {
|
||||||
|
errorf(tdata->global, "accept error: %08lx\n", GetLastError());
|
||||||
|
goto ThreadCleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
closesocket(tdata->socket_l); /* sclose here fails test 1498 */
|
||||||
|
tdata->socket_l = CURL_SOCKET_BAD;
|
||||||
|
if(shutdown(socket_w, SD_RECEIVE) == SOCKET_ERROR) {
|
||||||
|
errorf(tdata->global, "shutdown error: %08lx\n", GetLastError());
|
||||||
|
goto ThreadCleanup;
|
||||||
|
}
|
||||||
|
for(;;) {
|
||||||
|
r = ReadFile(tdata->stdin_handle, buffer, sizeof(buffer), &n, NULL);
|
||||||
|
if(r == 0)
|
||||||
|
break;
|
||||||
|
if(n == 0)
|
||||||
|
break;
|
||||||
|
nwritten = send(socket_w, buffer, n, 0);
|
||||||
|
if(nwritten == SOCKET_ERROR)
|
||||||
|
break;
|
||||||
|
if((DWORD)nwritten != n)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ThreadCleanup:
|
||||||
|
CloseHandle(tdata->stdin_handle);
|
||||||
|
tdata->stdin_handle = NULL;
|
||||||
|
if(tdata->socket_l != CURL_SOCKET_BAD) {
|
||||||
|
sclose(tdata->socket_l);
|
||||||
|
tdata->socket_l = CURL_SOCKET_BAD;
|
||||||
|
}
|
||||||
|
if(socket_w != CURL_SOCKET_BAD)
|
||||||
|
sclose(socket_w);
|
||||||
|
|
||||||
|
if(tdata) {
|
||||||
|
free(tdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The background thread that reads and buffers the true stdin. */
|
||||||
|
static HANDLE stdin_thread = NULL;
|
||||||
|
static curl_socket_t socket_r = CURL_SOCKET_BAD;
|
||||||
|
|
||||||
|
curl_socket_t win32_stdin_read_thread(struct GlobalConfig *global)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
bool r;
|
||||||
|
int rc = 0, socksize = 0;
|
||||||
|
struct win_thread_data *tdata = NULL;
|
||||||
|
SOCKADDR_IN selfaddr;
|
||||||
|
|
||||||
|
if(socket_r != CURL_SOCKET_BAD) {
|
||||||
|
assert(stdin_thread != NULL);
|
||||||
|
return socket_r;
|
||||||
|
}
|
||||||
|
assert(stdin_thread == NULL);
|
||||||
|
|
||||||
|
do {
|
||||||
|
/* Prepare handles for thread */
|
||||||
|
tdata = (struct win_thread_data*)calloc(1, sizeof(struct win_thread_data));
|
||||||
|
if(!tdata) {
|
||||||
|
errorf(global, "calloc() error");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Create the listening socket for the thread. When it starts, it will
|
||||||
|
* accept our connection and begin writing STDIN data to the connection. */
|
||||||
|
tdata->socket_l = WSASocketW(AF_INET, SOCK_STREAM,
|
||||||
|
IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
|
||||||
|
|
||||||
|
if(tdata->socket_l == CURL_SOCKET_BAD) {
|
||||||
|
errorf(global, "WSASocketW error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
socksize = sizeof(selfaddr);
|
||||||
|
memset(&selfaddr, 0, socksize);
|
||||||
|
selfaddr.sin_family = AF_INET;
|
||||||
|
selfaddr.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
/* Bind to any available loopback port */
|
||||||
|
result = bind(tdata->socket_l, (SOCKADDR*)&selfaddr, socksize);
|
||||||
|
if(result == SOCKET_ERROR) {
|
||||||
|
errorf(global, "bind error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bind to any available loopback port */
|
||||||
|
result = getsockname(tdata->socket_l, (SOCKADDR*)&selfaddr, &socksize);
|
||||||
|
if(result == SOCKET_ERROR) {
|
||||||
|
errorf(global, "getsockname error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = listen(tdata->socket_l, 1);
|
||||||
|
if(result == SOCKET_ERROR) {
|
||||||
|
errorf(global, "listen error: %08lx\n", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make a copy of the stdin handle to be used by win_stdin_thread_func */
|
||||||
|
r = DuplicateHandle(GetCurrentProcess(), GetStdHandle(STD_INPUT_HANDLE),
|
||||||
|
GetCurrentProcess(), &tdata->stdin_handle,
|
||||||
|
0, FALSE, DUPLICATE_SAME_ACCESS);
|
||||||
|
|
||||||
|
if(!r) {
|
||||||
|
errorf(global, "DuplicateHandle error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start up the thread. We don't bother keeping a reference to it
|
||||||
|
because it runs until program termination. From here on out all reads
|
||||||
|
from the stdin handle or file descriptor 0 will be reading from the
|
||||||
|
socket that is fed by the thread. */
|
||||||
|
stdin_thread = CreateThread(NULL, 0, win_stdin_thread_func,
|
||||||
|
tdata, 0, NULL);
|
||||||
|
if(!stdin_thread) {
|
||||||
|
errorf(global, "CreateThread error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Connect to the thread and rearrange our own STDIN handles */
|
||||||
|
socket_r = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if(socket_r == CURL_SOCKET_BAD) {
|
||||||
|
errorf(global, "socket error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hard close the socket on closesocket() */
|
||||||
|
setsockopt(socket_r, SOL_SOCKET, SO_DONTLINGER, 0, 0);
|
||||||
|
|
||||||
|
if(connect(socket_r, (SOCKADDR*)&selfaddr, socksize) == SOCKET_ERROR) {
|
||||||
|
errorf(global, "connect error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(shutdown(socket_r, SD_SEND) == SOCKET_ERROR) {
|
||||||
|
errorf(global, "shutdown error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the stdin handle to read from the socket. */
|
||||||
|
if(SetStdHandle(STD_INPUT_HANDLE, (HANDLE)socket_r) == 0) {
|
||||||
|
errorf(global, "SetStdHandle error: %08lx", GetLastError());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = 1;
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
if(rc != 1) {
|
||||||
|
if(socket_r != CURL_SOCKET_BAD && tdata) {
|
||||||
|
if(GetStdHandle(STD_INPUT_HANDLE) == (HANDLE)socket_r &&
|
||||||
|
tdata->stdin_handle) {
|
||||||
|
/* restore STDIN */
|
||||||
|
SetStdHandle(STD_INPUT_HANDLE, tdata->stdin_handle);
|
||||||
|
tdata->stdin_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sclose(socket_r);
|
||||||
|
socket_r = CURL_SOCKET_BAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(stdin_thread) {
|
||||||
|
TerminateThread(stdin_thread, 1);
|
||||||
|
stdin_thread = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tdata) {
|
||||||
|
if(tdata->stdin_handle)
|
||||||
|
CloseHandle(tdata->stdin_handle);
|
||||||
|
if(tdata->socket_l != CURL_SOCKET_BAD)
|
||||||
|
sclose(tdata->socket_l);
|
||||||
|
|
||||||
|
free(tdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CURL_SOCKET_BAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(socket_r != CURL_SOCKET_BAD);
|
||||||
|
return socket_r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* !CURL_WINDOWS_UWP && !UNDER_CE */
|
||||||
|
|
||||||
#endif /* _WIN32 */
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
#endif /* _WIN32 || MSDOS */
|
#endif /* _WIN32 || MSDOS */
|
||||||
|
|
|
@ -55,6 +55,10 @@ CURLcode FindWin32CACert(struct OperationConfig *config,
|
||||||
struct curl_slist *GetLoadedModulePaths(void);
|
struct curl_slist *GetLoadedModulePaths(void);
|
||||||
CURLcode win32_init(void);
|
CURLcode win32_init(void);
|
||||||
|
|
||||||
|
#if !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
|
||||||
|
curl_socket_t win32_stdin_read_thread(struct GlobalConfig *global);
|
||||||
|
#endif /* !CURL_WINDOWS_UWP && !UNDER_CE */
|
||||||
|
|
||||||
#endif /* _WIN32 */
|
#endif /* _WIN32 */
|
||||||
|
|
||||||
#endif /* _WIN32 || MSDOS */
|
#endif /* _WIN32 || MSDOS */
|
||||||
|
|
|
@ -572,8 +572,22 @@ static CURLcode post_per_transfer(struct GlobalConfig *global,
|
||||||
if(!curl || !config)
|
if(!curl || !config)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
if(per->infdopen)
|
if(per->uploadfile) {
|
||||||
close(per->infd);
|
if(!strcmp(per->uploadfile, ".") && per->infd > 0) {
|
||||||
|
#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
|
||||||
|
sclose(per->infd);
|
||||||
|
#else
|
||||||
|
warnf(per->config->global, "Closing per->infd != 0: FD == "
|
||||||
|
"%d. This behavior is only supported on desktop "
|
||||||
|
" Windows", per->infd);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(per->infdopen) {
|
||||||
|
close(per->infd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(per->skip)
|
if(per->skip)
|
||||||
goto skip;
|
goto skip;
|
||||||
|
@ -1066,6 +1080,26 @@ static void check_stdin_upload(struct GlobalConfig *global,
|
||||||
|
|
||||||
CURLX_SET_BINMODE(stdin);
|
CURLX_SET_BINMODE(stdin);
|
||||||
if(!strcmp(per->uploadfile, ".")) {
|
if(!strcmp(per->uploadfile, ".")) {
|
||||||
|
#if defined(_WIN32) && !defined(CURL_WINDOWS_UWP) && !defined(UNDER_CE)
|
||||||
|
/* non-blocking stdin behavior on Windows is challenging
|
||||||
|
Spawn a new thread that will read from stdin and write
|
||||||
|
out to a socket */
|
||||||
|
curl_socket_t f = win32_stdin_read_thread(global);
|
||||||
|
|
||||||
|
if(f == CURL_SOCKET_BAD)
|
||||||
|
warnf(global, "win32_stdin_read_thread returned INVALID_SOCKET "
|
||||||
|
"falling back to blocking mode");
|
||||||
|
else if(f > INT_MAX) {
|
||||||
|
warnf(global, "win32_stdin_read_thread returned identifier "
|
||||||
|
"larger than INT_MAX. This should not happen unless "
|
||||||
|
"the upper 32 bits of a Windows socket have started "
|
||||||
|
"being used for something... falling back to blocking "
|
||||||
|
"mode");
|
||||||
|
sclose(f);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
per->infd = (int)f;
|
||||||
|
#endif
|
||||||
if(curlx_nonblock((curl_socket_t)per->infd, TRUE) < 0)
|
if(curlx_nonblock((curl_socket_t)per->infd, TRUE) < 0)
|
||||||
warnf(global,
|
warnf(global,
|
||||||
"fcntl failed on fd=%d: %s", per->infd, strerror(errno));
|
"fcntl failed on fd=%d: %s", per->infd, strerror(errno));
|
||||||
|
|
Loading…
Reference in New Issue
Block a user