Make it possible to build curl for Windows CE using the CeGCC toolchain.
With both CMake and autotools, including tests and examples, also in CI.
The build configuration is the default one with Schannel enabled. No
3rd-party dependencies have been tested.
Also revive old code to make Schannel build with Windows CE, including
certificate verification.
Builds have been throughougly tested. But, I've made no functional tests
for this PR. Some parts (esp. file operations, like truncate and seek)
are stubbed out and likely broken as a result. Test servers build, but
they do not work on Windows CE. This patch substitutes `fstat()` calls
with `stat()`, which operate on filenames, not file handles. This may or
may not work and/or may not be secure.
About CeGCC: I used the latest available macOS binary build v0.59.1
r1397 from 2009, in native `mingw32ce` build mode. CeGCC is in effect
MinGW + GCC 4.4.0 + old/classic-mingw Windows headers. It targets
Windows CE v3.0 according to its `_WIN32_WCE` value. It means this PR
restores portions of old/classic-mingw support. It makes the Windows CE
codepath compatible with GCC 4.4.0. It also adds workaround for CMake,
which cannot identify and configure this toolchain out of the box.
Notes:
- CMake doesn't recognize CeGCC/mingw32ce, necessitating tricks as seen
with Amiga and MS-DOS.
- CMake doesn't set `MINGW` for mingw32ce. Set it and `MINGW32CE`
manually as a helper variable, in addition to `WINCE` which CMake sets
based on `CMAKE_SYSTEM_NAME`.
- CMake fails to create an implib for `libcurl.dll`, due to not
recognizing the platform as a Windowsy one. This patch adds the
necessary workaround to make it work.
- headers shipping with CeGCC miss some things curl needs for Schannel
support. Fixed by restoring and renovating code previously deleted
old-mingw code.
- it's sometime non-trivial to figure out if a fallout is WinCE,
mingw32ce, old-mingw, or GCC version-specific.
- WinCE is always Unicode. With exceptions: no `wmain`,
`GetProcAddress()`.
- `_fileno()` is said to convert from `FILE *` to `void *` which is
a Win32 file `HANDLE`. (This patch doesn't use this, but with further
effort it probably could be.)
https://stackoverflow.com/questions/3989545/how-do-i-get-the-file-handle-from-the-fopen-file-structure
- WinCE has no signals, current directory, stdio/CRT file handles, no
`_get_osfhandle()`, no `errno`, no `errno.h`. Some of this stuff is
standard C89, yet missing from this platform. Microsoft expects
Windows CE apps to use Win32 file API and `FILE *` exclusively.
- revived CeGCC here (not tested for this PR):
https://building.enlyze.com/posts/a-new-windows-ce-x86-compiler-in-2024/
On `UNDER_CE` vs. `_WIN32_WCE`: (This patch settled on `UNDER_CE`)
- A custom VS2008 WinCE toolchain does not set any of these.
The compiler binaries don't contain these strings, and has no compiler
option for targeting WinCE, hinting that a vanilla toolchain isn't
setting any of them either.
- `UNDER_CE` is automatically defined by the CeGCC compiler.
https://cegcc.sourceforge.net/docs/details.html
- `UNDER_CE` is similar to `_WIN32`, except it's not set automatically
by all compilers. It's not supposed to have any value, like a version.
(Though e.g. OpenSSL sets it to a version)
- `_WIN32_WCE` is the CE counterpart of the non-CE `_WIN32_WINNT` macro.
That does return the targeted Windows CE version.
- `_WIN32_WCE` is not defined by compilers, and relies on a header
setting it to a default, or the build to set it to the desired target
version. This is also how `_WIN32_WINNT` works.
- `_WIN32_WCE` default is set by `windef.h` in CeGCC.
- `_WIN32_WCE` isn't set to a default by MSVC Windows CE headers (the
ones I checked at least).
- CMake sets `_WIN32_WCE=<ver>`, `UNDER_CE`, `WINCE` for MSVC WinCE.
- `_WIN32_WCE` seems more popular in other projects, including CeGCC
itself. `zlib` is a notable exception amongst curl dependencies,
which uses `UNDER_CE`.
- Since `_WIN32_WCE` needs "certain" headers to have it defined, it's
undefined depending on headers included beforehand.
- `curl/curl.h` re-uses `_WIN32_WCE`'s as a self-guard, relying on
its not-(necessarily)-defined-by-default property:
25b445e479/include/curl/curl.h (L77)
Toolchain downloads:
- Windows:
https://downloads.sourceforge.net/cegcc/cegcc/0.59.1/cegcc_mingw32ce_cygwin1.7_r1399.tar.bz2
- macOS Intel:
https://downloads.sourceforge.net/cegcc/cegcc/0.59.1/cegcc_mingw32ce_snowleopard_r1397.tar.bz2
Closes #15975
777 lines
19 KiB
C
777 lines
19 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
|
*
|
|
* This software is licensed as described in the file COPYING, which
|
|
* you should have received as part of this distribution. The terms
|
|
* are also available at https://curl.se/docs/copyright.html.
|
|
*
|
|
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
|
* copies of the Software, and permit persons to whom the Software is
|
|
* furnished to do so, under the terms of the COPYING file.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
***************************************************************************/
|
|
|
|
#include "curl_setup.h"
|
|
#include "socketpair.h"
|
|
|
|
/***********************************************************************
|
|
* Only for threaded name resolves builds
|
|
**********************************************************************/
|
|
#ifdef CURLRES_THREADED
|
|
|
|
#ifdef HAVE_NETINET_IN_H
|
|
#include <netinet/in.h>
|
|
#endif
|
|
#ifdef HAVE_NETDB_H
|
|
#include <netdb.h>
|
|
#endif
|
|
#ifdef HAVE_ARPA_INET_H
|
|
#include <arpa/inet.h>
|
|
#endif
|
|
#ifdef __VMS
|
|
#include <in.h>
|
|
#include <inet.h>
|
|
#endif
|
|
|
|
#if defined(USE_THREADS_POSIX) && defined(HAVE_PTHREAD_H)
|
|
# include <pthread.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_GETADDRINFO
|
|
# define RESOLVER_ENOMEM EAI_MEMORY
|
|
#else
|
|
# define RESOLVER_ENOMEM ENOMEM
|
|
#endif
|
|
|
|
#include "urldata.h"
|
|
#include "sendf.h"
|
|
#include "hostip.h"
|
|
#include "hash.h"
|
|
#include "share.h"
|
|
#include "url.h"
|
|
#include "multiif.h"
|
|
#include "inet_ntop.h"
|
|
#include "curl_threads.h"
|
|
#include "connect.h"
|
|
#include "strdup.h"
|
|
|
|
#ifdef USE_ARES
|
|
#include <ares.h>
|
|
#ifdef USE_HTTPSRR
|
|
#define USE_HTTPSRR_ARES 1 /* the combo */
|
|
#endif
|
|
#endif
|
|
|
|
/* The last 3 #include files should be in this order */
|
|
#include "curl_printf.h"
|
|
#include "curl_memory.h"
|
|
#include "memdebug.h"
|
|
|
|
|
|
/*
|
|
* Curl_resolver_global_init()
|
|
* Called from curl_global_init() to initialize global resolver environment.
|
|
* Does nothing here.
|
|
*/
|
|
int Curl_resolver_global_init(void)
|
|
{
|
|
return CURLE_OK;
|
|
}
|
|
|
|
/*
|
|
* Curl_resolver_global_cleanup()
|
|
* Called from curl_global_cleanup() to destroy global resolver environment.
|
|
* Does nothing here.
|
|
*/
|
|
void Curl_resolver_global_cleanup(void)
|
|
{
|
|
}
|
|
|
|
/*
|
|
* Curl_resolver_init()
|
|
* Called from curl_easy_init() -> Curl_open() to initialize resolver
|
|
* URL-state specific environment ('resolver' member of the UrlState
|
|
* structure).
|
|
*/
|
|
CURLcode Curl_resolver_init(struct Curl_easy *easy, void **resolver)
|
|
{
|
|
(void)easy;
|
|
(void)resolver;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
/*
|
|
* Curl_resolver_cleanup()
|
|
* Called from curl_easy_cleanup() -> Curl_close() to cleanup resolver
|
|
* URL-state specific environment ('resolver' member of the UrlState
|
|
* structure).
|
|
*/
|
|
void Curl_resolver_cleanup(void *resolver)
|
|
{
|
|
(void)resolver;
|
|
}
|
|
|
|
/*
|
|
* Curl_resolver_duphandle()
|
|
* Called from curl_easy_duphandle() to duplicate resolver URL state-specific
|
|
* environment ('resolver' member of the UrlState structure).
|
|
*/
|
|
CURLcode Curl_resolver_duphandle(struct Curl_easy *easy, void **to, void *from)
|
|
{
|
|
(void)from;
|
|
return Curl_resolver_init(easy, to);
|
|
}
|
|
|
|
static void destroy_async_data(struct Curl_easy *);
|
|
|
|
/*
|
|
* Cancel all possibly still on-going resolves for this connection.
|
|
*/
|
|
void Curl_resolver_cancel(struct Curl_easy *data)
|
|
{
|
|
destroy_async_data(data);
|
|
}
|
|
|
|
/* This function is used to init a threaded resolve */
|
|
static bool init_resolve_thread(struct Curl_easy *data,
|
|
const char *hostname, int port,
|
|
const struct addrinfo *hints);
|
|
|
|
|
|
static struct thread_sync_data *conn_thread_sync_data(struct Curl_easy *data)
|
|
{
|
|
return &(data->state.async.thdata.tsd);
|
|
}
|
|
|
|
/* Destroy resolver thread synchronization data */
|
|
static
|
|
void destroy_thread_sync_data(struct thread_sync_data *tsd)
|
|
{
|
|
Curl_mutex_destroy(&tsd->mutx);
|
|
|
|
free(tsd->hostname);
|
|
|
|
if(tsd->res)
|
|
Curl_freeaddrinfo(tsd->res);
|
|
|
|
#ifndef CURL_DISABLE_SOCKETPAIR
|
|
/*
|
|
* close one end of the socket pair (may be done in resolver thread);
|
|
* the other end (for reading) is always closed in the parent thread.
|
|
*/
|
|
#ifndef HAVE_EVENTFD
|
|
if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
|
|
wakeup_close(tsd->sock_pair[1]);
|
|
}
|
|
#endif
|
|
#endif
|
|
memset(tsd, 0, sizeof(*tsd));
|
|
}
|
|
|
|
/* Initialize resolver thread synchronization data */
|
|
static
|
|
int init_thread_sync_data(struct thread_data *td,
|
|
const char *hostname,
|
|
int port,
|
|
const struct addrinfo *hints)
|
|
{
|
|
struct thread_sync_data *tsd = &td->tsd;
|
|
|
|
memset(tsd, 0, sizeof(*tsd));
|
|
|
|
td->init = TRUE;
|
|
tsd->port = port;
|
|
/* Treat the request as done until the thread actually starts so any early
|
|
* cleanup gets done properly.
|
|
*/
|
|
tsd->done = TRUE;
|
|
#ifdef HAVE_GETADDRINFO
|
|
DEBUGASSERT(hints);
|
|
tsd->hints = *hints;
|
|
#else
|
|
(void) hints;
|
|
#endif
|
|
|
|
Curl_mutex_init(&tsd->mutx);
|
|
|
|
#ifndef CURL_DISABLE_SOCKETPAIR
|
|
/* create socket pair or pipe */
|
|
if(wakeup_create(tsd->sock_pair, FALSE) < 0) {
|
|
tsd->sock_pair[0] = CURL_SOCKET_BAD;
|
|
tsd->sock_pair[1] = CURL_SOCKET_BAD;
|
|
goto err_exit;
|
|
}
|
|
#endif
|
|
tsd->sock_error = CURL_ASYNC_SUCCESS;
|
|
|
|
/* Copying hostname string because original can be destroyed by parent
|
|
* thread during gethostbyname execution.
|
|
*/
|
|
tsd->hostname = strdup(hostname);
|
|
if(!tsd->hostname)
|
|
goto err_exit;
|
|
|
|
return 1;
|
|
|
|
err_exit:
|
|
#ifndef CURL_DISABLE_SOCKETPAIR
|
|
if(tsd->sock_pair[0] != CURL_SOCKET_BAD) {
|
|
wakeup_close(tsd->sock_pair[0]);
|
|
tsd->sock_pair[0] = CURL_SOCKET_BAD;
|
|
}
|
|
#endif
|
|
destroy_thread_sync_data(tsd);
|
|
return 0;
|
|
}
|
|
|
|
static CURLcode getaddrinfo_complete(struct Curl_easy *data)
|
|
{
|
|
struct thread_sync_data *tsd = conn_thread_sync_data(data);
|
|
CURLcode result;
|
|
|
|
result = Curl_addrinfo_callback(data, tsd->sock_error, tsd->res);
|
|
/* The tsd->res structure has been copied to async.dns and perhaps the DNS
|
|
cache. Set our copy to NULL so destroy_thread_sync_data does not free it.
|
|
*/
|
|
tsd->res = NULL;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
#ifdef HAVE_GETADDRINFO
|
|
|
|
/*
|
|
* getaddrinfo_thread() resolves a name and then exits.
|
|
*
|
|
* For builds without ARES, but with USE_IPV6, create a resolver thread
|
|
* and wait on it.
|
|
*/
|
|
static
|
|
#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE)
|
|
DWORD
|
|
#else
|
|
unsigned int
|
|
#endif
|
|
CURL_STDCALL getaddrinfo_thread(void *arg)
|
|
{
|
|
struct thread_data *td = arg;
|
|
struct thread_sync_data *tsd = &td->tsd;
|
|
char service[12];
|
|
int rc;
|
|
|
|
msnprintf(service, sizeof(service), "%d", tsd->port);
|
|
|
|
rc = Curl_getaddrinfo_ex(tsd->hostname, service, &tsd->hints, &tsd->res);
|
|
|
|
if(rc) {
|
|
tsd->sock_error = SOCKERRNO ? SOCKERRNO : rc;
|
|
if(tsd->sock_error == 0)
|
|
tsd->sock_error = RESOLVER_ENOMEM;
|
|
}
|
|
else {
|
|
Curl_addrinfo_set_port(tsd->res, tsd->port);
|
|
}
|
|
|
|
Curl_mutex_acquire(&tsd->mutx);
|
|
if(tsd->done) {
|
|
/* too late, gotta clean up the mess */
|
|
Curl_mutex_release(&tsd->mutx);
|
|
destroy_thread_sync_data(tsd);
|
|
}
|
|
else {
|
|
#ifndef CURL_DISABLE_SOCKETPAIR
|
|
if(tsd->sock_pair[1] != CURL_SOCKET_BAD) {
|
|
#ifdef HAVE_EVENTFD
|
|
const uint64_t buf[1] = { 1 };
|
|
#else
|
|
const char buf[1] = { 1 };
|
|
#endif
|
|
/* DNS has been resolved, signal client task */
|
|
if(wakeup_write(tsd->sock_pair[1], buf, sizeof(buf)) < 0) {
|
|
/* update sock_erro to errno */
|
|
tsd->sock_error = SOCKERRNO;
|
|
}
|
|
}
|
|
#endif
|
|
tsd->done = TRUE;
|
|
Curl_mutex_release(&tsd->mutx);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else /* HAVE_GETADDRINFO */
|
|
|
|
/*
|
|
* gethostbyname_thread() resolves a name and then exits.
|
|
*/
|
|
static
|
|
#if defined(CURL_WINDOWS_UWP) || defined(UNDER_CE)
|
|
DWORD
|
|
#else
|
|
unsigned int
|
|
#endif
|
|
CURL_STDCALL gethostbyname_thread(void *arg)
|
|
{
|
|
struct thread_data *td = arg;
|
|
struct thread_sync_data *tsd = &td->tsd;
|
|
|
|
tsd->res = Curl_ipv4_resolve_r(tsd->hostname, tsd->port);
|
|
|
|
if(!tsd->res) {
|
|
tsd->sock_error = SOCKERRNO;
|
|
if(tsd->sock_error == 0)
|
|
tsd->sock_error = RESOLVER_ENOMEM;
|
|
}
|
|
|
|
Curl_mutex_acquire(&tsd->mutx);
|
|
if(tsd->done) {
|
|
/* too late, gotta clean up the mess */
|
|
Curl_mutex_release(&tsd->mutx);
|
|
destroy_thread_sync_data(tsd);
|
|
}
|
|
else {
|
|
tsd->done = TRUE;
|
|
Curl_mutex_release(&tsd->mutx);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif /* HAVE_GETADDRINFO */
|
|
|
|
/*
|
|
* destroy_async_data() cleans up async resolver data and thread handle.
|
|
*/
|
|
static void destroy_async_data(struct Curl_easy *data)
|
|
{
|
|
struct Curl_async *async = &data->state.async;
|
|
struct thread_data *td = &async->thdata;
|
|
if(td->init) {
|
|
bool done;
|
|
#ifndef CURL_DISABLE_SOCKETPAIR
|
|
curl_socket_t sock_rd = td->tsd.sock_pair[0];
|
|
#endif
|
|
|
|
#ifdef USE_HTTPSRR_ARES
|
|
if(data->state.async.thdata.channel) {
|
|
ares_destroy(data->state.async.thdata.channel);
|
|
data->state.async.thdata.channel = NULL;
|
|
}
|
|
#endif
|
|
/*
|
|
* if the thread is still blocking in the resolve syscall, detach it and
|
|
* let the thread do the cleanup...
|
|
*/
|
|
Curl_mutex_acquire(&td->tsd.mutx);
|
|
done = td->tsd.done;
|
|
td->tsd.done = TRUE;
|
|
Curl_mutex_release(&td->tsd.mutx);
|
|
|
|
if(!done) {
|
|
Curl_thread_destroy(td->thread_hnd);
|
|
}
|
|
else {
|
|
if(td->thread_hnd != curl_thread_t_null)
|
|
Curl_thread_join(&td->thread_hnd);
|
|
|
|
destroy_thread_sync_data(&td->tsd);
|
|
}
|
|
#ifndef CURL_DISABLE_SOCKETPAIR
|
|
/*
|
|
* ensure CURLMOPT_SOCKETFUNCTION fires CURL_POLL_REMOVE
|
|
* before the FD is invalidated to avoid EBADF on EPOLL_CTL_DEL
|
|
*/
|
|
Curl_multi_closed(data, sock_rd);
|
|
wakeup_close(sock_rd);
|
|
#endif
|
|
|
|
td->init = FALSE;
|
|
}
|
|
Curl_safefree(async->hostname);
|
|
}
|
|
|
|
#ifdef USE_HTTPSRR_ARES
|
|
static CURLcode resolve_httpsrr(struct Curl_easy *data,
|
|
struct Curl_async *async)
|
|
{
|
|
int status = ares_init_options(&async->thdata.channel, NULL, 0);
|
|
if(status != ARES_SUCCESS)
|
|
return CURLE_FAILED_INIT;
|
|
|
|
memset(&async->thdata.hinfo, 0, sizeof(struct Curl_https_rrinfo));
|
|
async->thdata.hinfo.port = -1;
|
|
ares_query_dnsrec(async->thdata.channel,
|
|
async->hostname, ARES_CLASS_IN,
|
|
ARES_REC_TYPE_HTTPS,
|
|
Curl_dnsrec_done_cb, data, NULL);
|
|
|
|
return CURLE_OK;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* init_resolve_thread() starts a new thread that performs the actual
|
|
* resolve. This function returns before the resolve is done.
|
|
*
|
|
* Returns FALSE in case of failure, otherwise TRUE.
|
|
*/
|
|
static bool init_resolve_thread(struct Curl_easy *data,
|
|
const char *hostname, int port,
|
|
const struct addrinfo *hints)
|
|
{
|
|
struct thread_data *td = &data->state.async.thdata;
|
|
int err = ENOMEM;
|
|
struct Curl_async *async = &data->state.async;
|
|
|
|
async->port = port;
|
|
async->done = FALSE;
|
|
async->dns = NULL;
|
|
td->thread_hnd = curl_thread_t_null;
|
|
td->start = Curl_now();
|
|
|
|
if(!init_thread_sync_data(td, hostname, port, hints)) {
|
|
free(td);
|
|
goto errno_exit;
|
|
}
|
|
|
|
free(async->hostname);
|
|
async->hostname = strdup(hostname);
|
|
if(!async->hostname)
|
|
goto err_exit;
|
|
|
|
/* The thread will set this TRUE when complete. */
|
|
td->tsd.done = FALSE;
|
|
|
|
#ifdef HAVE_GETADDRINFO
|
|
td->thread_hnd = Curl_thread_create(getaddrinfo_thread, td);
|
|
#else
|
|
td->thread_hnd = Curl_thread_create(gethostbyname_thread, td);
|
|
#endif
|
|
|
|
if(td->thread_hnd == curl_thread_t_null) {
|
|
/* The thread never started, so mark it as done here for proper cleanup. */
|
|
td->tsd.done = TRUE;
|
|
err = errno;
|
|
goto err_exit;
|
|
}
|
|
#ifdef USE_HTTPSRR_ARES
|
|
if(resolve_httpsrr(data, async))
|
|
infof(data, "Failed HTTPS RR operation");
|
|
#endif
|
|
return TRUE;
|
|
|
|
err_exit:
|
|
destroy_async_data(data);
|
|
|
|
errno_exit:
|
|
CURL_SETERRNO(err);
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* 'entry' may be NULL and then no data is returned
|
|
*/
|
|
static CURLcode thread_wait_resolv(struct Curl_easy *data,
|
|
struct Curl_dns_entry **entry,
|
|
bool report)
|
|
{
|
|
struct thread_data *td;
|
|
CURLcode result = CURLE_OK;
|
|
|
|
DEBUGASSERT(data);
|
|
td = &data->state.async.thdata;
|
|
DEBUGASSERT(td);
|
|
DEBUGASSERT(td->thread_hnd != curl_thread_t_null);
|
|
|
|
/* wait for the thread to resolve the name */
|
|
if(Curl_thread_join(&td->thread_hnd)) {
|
|
if(entry)
|
|
result = getaddrinfo_complete(data);
|
|
}
|
|
else
|
|
DEBUGASSERT(0);
|
|
|
|
data->state.async.done = TRUE;
|
|
|
|
if(entry)
|
|
*entry = data->state.async.dns;
|
|
|
|
if(!data->state.async.dns && report)
|
|
/* a name was not resolved, report error */
|
|
result = Curl_resolver_error(data);
|
|
|
|
destroy_async_data(data);
|
|
|
|
if(!data->state.async.dns && report)
|
|
connclose(data->conn, "asynch resolve failed");
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/*
|
|
* Until we gain a way to signal the resolver threads to stop early, we must
|
|
* simply wait for them and ignore their results.
|
|
*/
|
|
void Curl_resolver_kill(struct Curl_easy *data)
|
|
{
|
|
struct thread_data *td = &data->state.async.thdata;
|
|
|
|
/* If we are still resolving, we must wait for the threads to fully clean up,
|
|
unfortunately. Otherwise, we can simply cancel to clean up any resolver
|
|
data. */
|
|
if((td->thread_hnd != curl_thread_t_null) && !data->set.quick_exit)
|
|
(void)thread_wait_resolv(data, NULL, FALSE);
|
|
else
|
|
Curl_resolver_cancel(data);
|
|
}
|
|
|
|
/*
|
|
* Curl_resolver_wait_resolv()
|
|
*
|
|
* Waits for a resolve to finish. This function should be avoided since using
|
|
* this risk getting the multi interface to "hang".
|
|
*
|
|
* If 'entry' is non-NULL, make it point to the resolved dns entry
|
|
*
|
|
* Returns CURLE_COULDNT_RESOLVE_HOST if the host was not resolved,
|
|
* CURLE_OPERATION_TIMEDOUT if a time-out occurred, or other errors.
|
|
*
|
|
* This is the version for resolves-in-a-thread.
|
|
*/
|
|
CURLcode Curl_resolver_wait_resolv(struct Curl_easy *data,
|
|
struct Curl_dns_entry **entry)
|
|
{
|
|
return thread_wait_resolv(data, entry, TRUE);
|
|
}
|
|
|
|
/*
|
|
* Curl_resolver_is_resolved() is called repeatedly to check if a previous
|
|
* name resolve request has completed. It should also make sure to time-out if
|
|
* the operation seems to take too long.
|
|
*/
|
|
CURLcode Curl_resolver_is_resolved(struct Curl_easy *data,
|
|
struct Curl_dns_entry **entry)
|
|
{
|
|
struct thread_data *td = &data->state.async.thdata;
|
|
bool done = FALSE;
|
|
|
|
DEBUGASSERT(entry);
|
|
*entry = NULL;
|
|
|
|
if(!td) {
|
|
DEBUGASSERT(td);
|
|
return CURLE_COULDNT_RESOLVE_HOST;
|
|
}
|
|
#ifdef USE_HTTPSRR_ARES
|
|
if(Curl_ares_perform(data->state.async.thdata.channel, 0) < 0)
|
|
return CURLE_UNRECOVERABLE_POLL;
|
|
#endif
|
|
|
|
Curl_mutex_acquire(&td->tsd.mutx);
|
|
done = td->tsd.done;
|
|
Curl_mutex_release(&td->tsd.mutx);
|
|
|
|
if(done) {
|
|
CURLcode result = td->result;
|
|
getaddrinfo_complete(data);
|
|
|
|
if(!result && !data->state.async.dns)
|
|
result = Curl_resolver_error(data);
|
|
|
|
if(result) {
|
|
destroy_async_data(data);
|
|
return result;
|
|
}
|
|
#ifdef USE_HTTPSRR_ARES
|
|
{
|
|
struct Curl_https_rrinfo *lhrr = Curl_httpsrr_dup_move(&td->hinfo);
|
|
if(!lhrr) {
|
|
destroy_async_data(data);
|
|
return CURLE_OUT_OF_MEMORY;
|
|
}
|
|
data->state.async.dns->hinfo = lhrr;
|
|
}
|
|
#endif
|
|
destroy_async_data(data);
|
|
*entry = data->state.async.dns;
|
|
}
|
|
else {
|
|
/* poll for name lookup done with exponential backoff up to 250ms */
|
|
/* should be fine even if this converts to 32-bit */
|
|
timediff_t elapsed = Curl_timediff(Curl_now(),
|
|
data->progress.t_startsingle);
|
|
if(elapsed < 0)
|
|
elapsed = 0;
|
|
|
|
if(td->poll_interval == 0)
|
|
/* Start at 1ms poll interval */
|
|
td->poll_interval = 1;
|
|
else if(elapsed >= td->interval_end)
|
|
/* Back-off exponentially if last interval expired */
|
|
td->poll_interval *= 2;
|
|
|
|
if(td->poll_interval > 250)
|
|
td->poll_interval = 250;
|
|
|
|
td->interval_end = elapsed + td->poll_interval;
|
|
Curl_expire(data, td->poll_interval, EXPIRE_ASYNC_NAME);
|
|
}
|
|
|
|
return CURLE_OK;
|
|
}
|
|
|
|
int Curl_resolver_getsock(struct Curl_easy *data, curl_socket_t *socks)
|
|
{
|
|
int ret_val = 0;
|
|
struct thread_data *td = &data->state.async.thdata;
|
|
#if !defined(CURL_DISABLE_SOCKETPAIR) || defined(USE_HTTPSRR_ARES)
|
|
int socketi = 0;
|
|
#else
|
|
(void)socks;
|
|
#endif
|
|
|
|
#ifdef USE_HTTPSRR_ARES
|
|
if(data->state.async.thdata.channel) {
|
|
ret_val = Curl_ares_getsock(data, data->state.async.thdata.channel, socks);
|
|
for(socketi = 0; socketi < (MAX_SOCKSPEREASYHANDLE - 1); socketi++)
|
|
if(!ARES_GETSOCK_READABLE(ret_val, socketi) &&
|
|
!ARES_GETSOCK_WRITABLE(ret_val, socketi))
|
|
break;
|
|
}
|
|
#endif
|
|
#ifndef CURL_DISABLE_SOCKETPAIR
|
|
/* return read fd to client for polling the DNS resolution status */
|
|
socks[socketi] = td->tsd.sock_pair[0];
|
|
ret_val |= GETSOCK_READSOCK(socketi);
|
|
#else
|
|
{
|
|
timediff_t milli;
|
|
timediff_t ms = Curl_timediff(Curl_now(), td->start);
|
|
if(ms < 3)
|
|
milli = 0;
|
|
else if(ms <= 50)
|
|
milli = ms/3;
|
|
else if(ms <= 250)
|
|
milli = 50;
|
|
else
|
|
milli = 200;
|
|
Curl_expire(data, milli, EXPIRE_ASYNC_NAME);
|
|
}
|
|
#endif
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
#ifndef HAVE_GETADDRINFO
|
|
/*
|
|
* Curl_getaddrinfo() - for platforms without getaddrinfo
|
|
*/
|
|
struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data,
|
|
const char *hostname,
|
|
int port,
|
|
int *waitp)
|
|
{
|
|
*waitp = 0; /* default to synchronous response */
|
|
|
|
/* fire up a new resolver thread! */
|
|
if(init_resolve_thread(data, hostname, port, NULL)) {
|
|
*waitp = 1; /* expect asynchronous response */
|
|
return NULL;
|
|
}
|
|
|
|
failf(data, "getaddrinfo() thread failed");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#else /* !HAVE_GETADDRINFO */
|
|
|
|
/*
|
|
* Curl_resolver_getaddrinfo() - for getaddrinfo
|
|
*/
|
|
struct Curl_addrinfo *Curl_resolver_getaddrinfo(struct Curl_easy *data,
|
|
const char *hostname,
|
|
int port,
|
|
int *waitp)
|
|
{
|
|
struct addrinfo hints;
|
|
int pf = PF_INET;
|
|
*waitp = 0; /* default to synchronous response */
|
|
|
|
#ifdef CURLRES_IPV6
|
|
if((data->conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
|
|
/* The stack seems to be IPv6-enabled */
|
|
if(data->conn->ip_version == CURL_IPRESOLVE_V6)
|
|
pf = PF_INET6;
|
|
else
|
|
pf = PF_UNSPEC;
|
|
}
|
|
#endif /* CURLRES_IPV6 */
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
hints.ai_family = pf;
|
|
hints.ai_socktype = (data->conn->transport == TRNSPRT_TCP) ?
|
|
SOCK_STREAM : SOCK_DGRAM;
|
|
|
|
/* fire up a new resolver thread! */
|
|
if(init_resolve_thread(data, hostname, port, &hints)) {
|
|
*waitp = 1; /* expect asynchronous response */
|
|
return NULL;
|
|
}
|
|
|
|
failf(data, "getaddrinfo() thread failed to start");
|
|
return NULL;
|
|
|
|
}
|
|
|
|
#endif /* !HAVE_GETADDRINFO */
|
|
|
|
CURLcode Curl_set_dns_servers(struct Curl_easy *data,
|
|
char *servers)
|
|
{
|
|
(void)data;
|
|
(void)servers;
|
|
return CURLE_NOT_BUILT_IN;
|
|
|
|
}
|
|
|
|
CURLcode Curl_set_dns_interface(struct Curl_easy *data,
|
|
const char *interf)
|
|
{
|
|
(void)data;
|
|
(void)interf;
|
|
return CURLE_NOT_BUILT_IN;
|
|
}
|
|
|
|
CURLcode Curl_set_dns_local_ip4(struct Curl_easy *data,
|
|
const char *local_ip4)
|
|
{
|
|
(void)data;
|
|
(void)local_ip4;
|
|
return CURLE_NOT_BUILT_IN;
|
|
}
|
|
|
|
CURLcode Curl_set_dns_local_ip6(struct Curl_easy *data,
|
|
const char *local_ip6)
|
|
{
|
|
(void)data;
|
|
(void)local_ip6;
|
|
return CURLE_NOT_BUILT_IN;
|
|
}
|
|
|
|
#endif /* CURLRES_THREADED */
|