all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* Async DNS for OSX
@ 2016-05-25  9:45 Magnus Henoch
  2016-05-25 18:27 ` Paul Eggert
  0 siblings, 1 reply; 6+ messages in thread
From: Magnus Henoch @ 2016-05-25  9:45 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1782 bytes --]

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



[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: async-dns-osx.patch --]
[-- Type: text/x-patch, Size: 10494 bytes --]

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 <http://www.gnu.org/licenses/>.  */
 #include <sig2str.h>
 #include <verify.h>
 
+#ifdef HAVE_CFHOSTCREATEWITHNAME
+#include <CFNetwork/CFHost.h>
+#endif
+
 #endif	/* subprocesses */
 
 #include "systime.h"
@@ -125,6 +129,10 @@ along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.  */
 #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 <http://www.gnu.org/licenses/>.  */
 
 #include <unistd.h>
 
+#ifdef HAVE_CFHOSTCREATEWITHNAME
+#include <CFNetwork/CFHost.h>
+#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

^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2016-05-26 14:41 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-05-25  9:45 Async DNS for OSX Magnus Henoch
2016-05-25 18:27 ` Paul Eggert
2016-05-25 18:34   ` Lars Ingebrigtsen
2016-05-25 23:37     ` Paul Eggert
2016-05-26 10:46       ` Lars Ingebrigtsen
2016-05-26 14:41         ` Paul Eggert

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.