From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Magnus Henoch Newsgroups: gmane.emacs.devel Subject: Async DNS for OSX Date: Wed, 25 May 2016 10:45:55 +0100 Message-ID: NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Trace: ger.gmane.org 1464169584 16562 80.91.229.3 (25 May 2016 09:46:24 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Wed, 25 May 2016 09:46:24 +0000 (UTC) To: emacs-devel@gnu.org Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Wed May 25 11:46:15 2016 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1b5VOI-0003jf-RH for ged-emacs-devel@m.gmane.org; Wed, 25 May 2016 11:46:15 +0200 Original-Received: from localhost ([::1]:58487 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1b5VOI-0007Qj-41 for ged-emacs-devel@m.gmane.org; Wed, 25 May 2016 05:46:14 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:55596) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1b5VOB-0007QY-AY for emacs-devel@gnu.org; Wed, 25 May 2016 05:46:09 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1b5VO7-00089g-1u for emacs-devel@gnu.org; Wed, 25 May 2016 05:46:06 -0400 Original-Received: from mail-wm0-x231.google.com ([2a00:1450:400c:c09::231]:33794) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1b5VO6-00089c-FE for emacs-devel@gnu.org; Wed, 25 May 2016 05:46:02 -0400 Original-Received: by mail-wm0-x231.google.com with SMTP id z87so17929458wmh.1 for ; Wed, 25 May 2016 02:46:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:subject:date:message-id:mime-version; bh=FWKfMGiF6WxefR7InLVx5fGwjUrTyADRxZ/HAqzWCk4=; b=CzHTNXGFMFeHiGdnG3nRd/+x64n8F6L5jksnwgvVwfmjzLrglSjhAkFyRMbKppZjah 60DOZSdSaylQb6covz2PMYApGdg+xK9SZp+JhxxqUZIvvKZordijeT8XeFQ/XGSDFFv3 r7uCYc7MCbEbPvzxVT8FJfBaLF/T3tWDycGENdxcf378Q1eF+ru/WSHxk6coXsF8oeU7 rQ6PjDcBGPJhK0r1m2aP7r4YljO9jk+CSEfKen1aEj2imeREk4cvU/ioiNF0vgCt8+b0 Uw0USTQsnWj13l6cHZiq+KOE/xeW7mhKLPjoc5rSKmod8+2AQNlkyh5MFavSUYTyfmDd MQTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:subject:date:message-id:mime-version; bh=FWKfMGiF6WxefR7InLVx5fGwjUrTyADRxZ/HAqzWCk4=; b=hvOH7v/E9SqikdxjNUavLgaakzmOeT8OMfTNK1uZqD0Wo5lzfztaGQzHRbaM1p9Aer pKCMauPkH78KkwzumgB9QazRRFoHQHHaO22FQqqWQN5I5YY/kA8PdGV/frctN9jsN6q2 oIKG7m91PbuYofkUB9ouBGJ2W74EQehPECaOZZfhNUV+pTBGQCA8Y0tJTTUk65L+K25v Vd5zcDvlJmP832C6lX2um0mBiNIIygyCJAH1w5dVfWgvwu+yrnCGYtpKk6gTOtprdXR7 JYeDDK7SY6uoAmEuRq3ftkKgfVvCSds6/6dvpyg8xGOF6SsKUv6zUvcPca9jYpY3R/n8 GmLQ== X-Gm-Message-State: ALyK8tL2HbxAf3kiopl+7w29Q66q+ejITIDT6kC/so+sh8J6C1N53rZEVvbT5RgyGu07kA== X-Received: by 10.194.81.72 with SMTP id y8mr2924238wjx.83.1464169561402; Wed, 25 May 2016 02:46:01 -0700 (PDT) Original-Received: from poki-sona-sin.local ([213.205.232.55]) by smtp.gmail.com with ESMTPSA id g129sm8080213wme.1.2016.05.25.02.45.58 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 25 May 2016 02:45:59 -0700 (PDT) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] X-Received-From: 2a00:1450:400c:c09::231 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:203998 Archived-At: --=-=-= Content-Type: text/plain; format=flowed Hi all, Since Emacs gained the capability to make asynchronous DNS lookups under GNU/Linux, I've been wishing for the same thing under OSX. Here is a little patch that I've hacked together, that I'd like some feedback on. In particular: 1. In Fmake_network_process, there's an XXX comment about deallocating my CFStringRef. I couldn't figure out from the documentation whether CFHostCreateWithName takes ownership of the string, or just copies it. 2. There is a callback function, host_client_callback, that writes error information into memory referred to by the process struct. There is a potential for a race condition here: what if the callback is called after the process has been deallocated? I've tried to avoid that by cancelling name resolution before deallocating the memory (see free_dns_request), but I suspect that's not enough. Is there a clever way to avoid that? 3. Unlike getaddrinfo_a, this CFHost thing doesn't resolve service names to port numbers. Therefore, I arrange to call getservbyname in Fmake_network_process, and insert the port number into a copy of the sockaddr struct in check_for_dns. I _think_ this should work for both IPv4 and IPv6, but I haven't tested IPv6. Is there a nicer way to do this? It took me a while to figure out the port number thing: before I added the port number, the process seemed to silently do nothing. That's because non-blocking connect would fail, and connect_network_socket would treat that as having to do a synchronous connect, which ended up not happening. I'm not sure if this is a bug or a feature. Any other comments are appreciated as well. I'll have sporadic Internet access during the coming week or so, will probably look at this again after that time. Regards, Magnus --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=async-dns-osx.patch diff --git a/configure.ac b/configure.ac index 5a6a72a..94c2947 100644 --- a/configure.ac +++ b/configure.ac @@ -2436,6 +2436,12 @@ if test "${HAVE_GETADDRINFO_A}" = "yes"; then AC_SUBST(GETADDRINFO_A_LIBS) fi +AC_CHECK_HEADER(CFNetwork/CFHost.h, + dnl XXX: Do we need to test for existence here? + LIBS="$LIBS -framework CFNetwork" + AC_DEFINE(HAVE_CFHOSTCREATEWITHNAME, 1, +[Define to 1 if you have CFHostCreateWithName for asynchronous DNS resolution.])) + HAVE_GTK=no GTK_OBJ= gtk_term_header=$term_header diff --git a/src/process.c b/src/process.c index 2b674e5..de27c31 100644 --- a/src/process.c +++ b/src/process.c @@ -87,6 +87,10 @@ along with GNU Emacs. If not, see . */ #include #include +#ifdef HAVE_CFHOSTCREATEWITHNAME +#include +#endif + #endif /* subprocesses */ #include "systime.h" @@ -125,6 +129,10 @@ along with GNU Emacs. If not, see . */ #define ASYNC_RETRY_NSEC 100000000 #endif +#ifdef HAVE_CFHOSTCREATEWITHNAME +static void host_client_callback (CFHostRef, CFHostInfoType, const CFStreamError *, void *); +#endif + #ifdef WINDOWSNT extern int sys_select (int, fd_set *, fd_set *, fd_set *, struct timespec *, void *); @@ -733,15 +741,24 @@ remove_process (register Lisp_Object proc) deactivate_process (proc); } -#ifdef HAVE_GETADDRINFO_A +#if defined HAVE_GETADDRINFO_A || defined HAVE_CFHOSTCREATEWITHNAME static void free_dns_request (Lisp_Object proc) { struct Lisp_Process *p = XPROCESS (proc); +#ifdef HAVE_GETADDRINFO_A if (p->dns_request->ar_result) freeaddrinfo (p->dns_request->ar_result); xfree (p->dns_request); +#elif defined HAVE_CFHOSTCREATEWITHNAME + CFRunLoopRef run_loop = CFRunLoopGetCurrent (); + CFHostUnscheduleFromRunLoop (p->dns_request, run_loop, kCFRunLoopDefaultMode); + CFHostCancelInfoResolution (p->dns_request, kCFHostAddresses); + CFRelease (p->dns_request); + xfree (p->dns_error); + p->dns_error = NULL; +#endif p->dns_request = NULL; } #endif @@ -853,6 +870,10 @@ nil, indicating the current buffer's process. */) if (canceled) free_dns_request (process); } +#elif defined HAVE_CFHOSTCREATEWITHNAME + if (p->dns_request) + /* free_dns_request will cancel the request for us. */ + free_dns_request (process); #endif p->raw_status_new = 0; @@ -3611,6 +3632,9 @@ usage: (make-network-process &rest ARGS) */) int ai_protocol = 0; #ifdef HAVE_GETADDRINFO_A struct gaicb *dns_request = NULL; +#elif defined HAVE_CFHOSTCREATEWITHNAME + CFHostRef dns_request = NULL; + CFStreamError *dns_error = NULL; #endif ptrdiff_t count = SPECPDL_INDEX (); @@ -3791,7 +3815,41 @@ usage: (make-network-process &rest ARGS) */) goto open_socket; } -#endif /* HAVE_GETADDRINFO_A */ +#elif defined(HAVE_CFHOSTCREATEWITHNAME) + if (!NILP (host) && !NILP (Fplist_get (contact, QCnowait))) + { + CFStringRef stringref; + + stringref = CFStringCreateWithCString (NULL, SSDATA (host), + kCFStringEncodingASCII); + if (! stringref) + error ("%s CFStringCreateWithCString error", SSDATA (host)); + /* XXX: when should I deallocate stringref? */ + dns_request = CFHostCreateWithName (NULL, stringref); + if (! dns_request) + error ("%s CFHostCreateWithName error", SSDATA (host)); + + dns_error = xmalloc (sizeof *dns_error); + memset (dns_error, 0, sizeof *dns_error); + CFHostClientContext client_context; + memset (&client_context, 0, sizeof client_context); + client_context.version = 0; + client_context.info = (void*)dns_error; + if (! CFHostSetClient (dns_request, host_client_callback, &client_context)) + error ("%s CFHostSetClient error", SSDATA (host)); + + CFRunLoopRef run_loop = CFRunLoopGetCurrent (); + CFHostScheduleWithRunLoop (dns_request, run_loop, kCFRunLoopDefaultMode); + + /* TODO: need error information? */ + if (! CFHostStartInfoResolution (dns_request, kCFHostAddresses, NULL)) + error ("%s CFHostStartInfoResolution error", SSDATA (host)); + + /* Unlike getaddrinfo_a, this doesn't resolve the service name + for us. */ + goto resolve_service; + } +#endif /* HAVE_GETADDRINFO_A | HAVE_CFHOSTCREATEWITHNAME */ /* If we have a host, use getaddrinfo to resolve both host and service. Otherwise, use getservbyname to lookup the service. */ @@ -3842,6 +3900,8 @@ usage: (make-network-process &rest ARGS) */) /* No hostname has been specified (e.g., a local server process). */ + resolve_service: + if (EQ (service, Qt)) port = 0; else if (INTEGERP (service)) @@ -3903,6 +3963,9 @@ usage: (make-network-process &rest ARGS) */) p->ai_protocol = ai_protocol; #ifdef HAVE_GETADDRINFO_A p->dns_request = NULL; +#elif defined HAVE_CFHOSTCREATEWITHNAME + p->dns_request = NULL; + p->dns_error = NULL; #endif #ifdef HAVE_GNUTLS tem = Fplist_get (contact, QCtls_parameters); @@ -3930,12 +3993,15 @@ usage: (make-network-process &rest ARGS) */) && !NILP (Fplist_get (contact, QCnowait))) p->is_non_blocking_client = true; -#ifdef HAVE_GETADDRINFO_A +#if defined HAVE_GETADDRINFO_A || defined HAVE_CFHOSTCREATEWITHNAME /* With async address resolution, the list of addresses is empty, so postpone connecting to the server. */ if (!p->is_server && NILP (ip_addresses)) { p->dns_request = dns_request; +#ifdef HAVE_CFHOSTCREATEWITHNAME + p->dns_error = dns_error; +#endif p->status = Qconnect; return proc; } @@ -4644,7 +4710,21 @@ server_accept_connection (Lisp_Object server, int channel) exec_sentinel (proc, concat3 (open_from, host_string, nl)); } -#ifdef HAVE_GETADDRINFO_A +#ifdef HAVE_CFHOSTCREATEWITHNAME +static void +host_client_callback (CFHostRef theHost, CFHostInfoType typeInfo, const CFStreamError *error, void *info) +{ + if (error) + { + CFStreamError *dns_error = (CFStreamError *)info; + /* XXX: how can I know that the process hasn't been deallocated? */ + dns_error->domain = error->domain; + dns_error->error = error->error; + } +} +#endif + +#if defined HAVE_GETADDRINFO_A || defined HAVE_CFHOSTCREATEWITHNAME static Lisp_Object check_for_dns (Lisp_Object proc) { @@ -4655,6 +4735,7 @@ check_for_dns (Lisp_Object proc) if (! p->dns_request) return Qnil; +#ifdef HAVE_GETADDRINFO_A int ret = gai_error (p->dns_request); if (ret == EAI_INPROGRESS) return Qt; @@ -4683,6 +4764,55 @@ check_for_dns (Lisp_Object proc) build_string (p->dns_request->ar_name), build_string (" failed"))))); } +#elif defined HAVE_CFHOSTCREATEWITHNAME + + Boolean has_been_resolved; + CFArrayRef res; + + res = CFHostGetAddressing (p->dns_request, &has_been_resolved); + + /* XXX: if has_been_resolved is false, does that mean there was an error? */ + if (has_been_resolved && res) + { + CFIndex n; + n = CFArrayGetCount (res); + for (CFIndex i = 0; i < n; i++) + { + CFDataRef entry = CFArrayGetValueAtIndex (res, i); + const struct sockaddr *addr = (const struct sockaddr *) CFDataGetBytePtr (entry); + CFIndex len = CFDataGetLength (entry); + /* Need to insert the port number. */ + struct sockaddr *addr_with_port = xmalloc (len); + memcpy (addr_with_port, addr, len); + /* XXX: this should work for IPv4 and IPv6, I think */ + ((struct sockaddr_in*)addr_with_port)->sin_port = htons (p->port); + ip_addresses = Fcons (conv_sockaddr_to_lisp + (addr_with_port, len), + ip_addresses); + xfree (addr_with_port); + + ip_addresses = Fnreverse (ip_addresses); + } + } + else if (p->dns_error->domain) + { + add_to_log ("async DNS resulution error"); /* XXX */ + deactivate_process (proc); + pset_status (p, (list2 + (Qfailed, + concat2 ( + concat2 (build_string ("Name lookup failed; error domain "), + Fnumber_to_string (make_number (p->dns_error->domain))), + concat2 (build_string (", error number "), + Fnumber_to_string (make_number (p->dns_error->error))))))); + } + else + { + /* Still in progress ??? */ + add_to_log ("Name resolution still in progress"); /* XXX */ + return Qt; + } +#endif free_dns_request (proc); @@ -4693,7 +4823,7 @@ check_for_dns (Lisp_Object proc) return ip_addresses; } -#endif /* HAVE_GETADDRINFO_A */ +#endif /* HAVE_GETADDRINFO_A || HAVE_CFHOSTCREATEWITHNAME */ static void wait_for_socket_fds (Lisp_Object process, char const *name) @@ -4805,9 +4935,10 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, Lisp_Object proc; struct timespec timeout, end_time, timer_delay; struct timespec got_output_end_time = invalid_timespec (); +#undef INFINITY enum { MINIMUM = -1, TIMEOUT, INFINITY } wait; int got_some_output = -1; -#if defined HAVE_GETADDRINFO_A || defined HAVE_GNUTLS +#if defined HAVE_GETADDRINFO_A || defined HAVE_CFHOSTCREATEWITHNAME || defined HAVE_GNUTLS bool retry_for_async; #endif ptrdiff_t count = SPECPDL_INDEX (); @@ -4857,7 +4988,7 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, if (! NILP (wait_for_cell) && ! NILP (XCAR (wait_for_cell))) break; -#if defined HAVE_GETADDRINFO_A || defined HAVE_GNUTLS +#if defined HAVE_GETADDRINFO_A || defined HAVE_CFHOSTCREATEWITHNAME || defined HAVE_GNUTLS { Lisp_Object process_list_head, aproc; struct Lisp_Process *p; @@ -4869,7 +5000,7 @@ wait_reading_process_output (intmax_t time_limit, int nsecs, int read_kbd, if (! wait_proc || p == wait_proc) { -#ifdef HAVE_GETADDRINFO_A +#if defined HAVE_GETADDRINFO_A || defined HAVE_CFHOSTCREATEWITHNAME /* Check for pending DNS requests. */ if (p->dns_request) { diff --git a/src/process.h b/src/process.h index bf1eadc..e47a53e 100644 --- a/src/process.h +++ b/src/process.h @@ -25,6 +25,10 @@ along with GNU Emacs. If not, see . */ #include +#ifdef HAVE_CFHOSTCREATEWITHNAME +#include +#endif + #ifdef HAVE_GNUTLS #include "gnutls.h" #endif @@ -180,6 +184,9 @@ struct Lisp_Process /* Whether the socket is waiting for response from an asynchronous DNS call. */ struct gaicb *dns_request; +#elif defined (HAVE_CFHOSTCREATEWITHNAME) + CFHostRef dns_request; + CFStreamError *dns_error; #endif #ifdef HAVE_GNUTLS --=-=-=--