mirror of
https://github.com/curl/curl.git
synced 2025-09-06 20:34:59 +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
|
||||
}
|
||||
|
||||
rc = read(per->infd, buffer, sz*nmemb);
|
||||
if(rc < 0) {
|
||||
if(errno == EAGAIN) {
|
||||
CURL_SETERRNO(0);
|
||||
config->readbusy = TRUE;
|
||||
return CURL_READFUNC_PAUSE;
|
||||
/* If we are on Windows, and using `-T .`, then per->infd points to a socket
|
||||
connected to stdin via a reader thread, and needs to be read with recv()
|
||||
Make sure we are in non-blocking mode and infd is not regular stdin
|
||||
On Linux per->infd should be stdin (0) and the block below should not
|
||||
execute */
|
||||
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) &&
|
||||
(per->uploadedsofar + rc > per->uploadfilesize)) {
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
|
||||
#include "tool_bname.h"
|
||||
#include "tool_doswin.h"
|
||||
#include "tool_msgs.h"
|
||||
|
||||
#include <curlx.h>
|
||||
#include <memdebug.h> /* keep this as LAST include */
|
||||
|
@ -741,6 +742,219 @@ CURLcode win32_init(void)
|
|||
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 || MSDOS */
|
||||
|
|
|
@ -55,6 +55,10 @@ CURLcode FindWin32CACert(struct OperationConfig *config,
|
|||
struct curl_slist *GetLoadedModulePaths(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 || MSDOS */
|
||||
|
|
|
@ -572,8 +572,22 @@ static CURLcode post_per_transfer(struct GlobalConfig *global,
|
|||
if(!curl || !config)
|
||||
return result;
|
||||
|
||||
if(per->infdopen)
|
||||
close(per->infd);
|
||||
if(per->uploadfile) {
|
||||
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)
|
||||
goto skip;
|
||||
|
@ -1066,6 +1080,26 @@ static void check_stdin_upload(struct GlobalConfig *global,
|
|||
|
||||
CURLX_SET_BINMODE(stdin);
|
||||
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)
|
||||
warnf(global,
|
||||
"fcntl failed on fd=%d: %s", per->infd, strerror(errno));
|
||||
|
|
Loading…
Reference in New Issue
Block a user