* 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
* Re: Async DNS for OSX
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
0 siblings, 1 reply; 6+ messages in thread
From: Paul Eggert @ 2016-05-25 18:27 UTC (permalink / raw
To: Magnus Henoch, emacs-devel
Would it make sense to do this as a gnulib module for getaddrinfo_a?
That way, Emacs could just continue to use the GNU/Linux interface, and
other programs needing async DNS could do so as well, without too much
trouble.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: Async DNS for OSX
2016-05-25 18:27 ` Paul Eggert
@ 2016-05-25 18:34 ` Lars Ingebrigtsen
2016-05-25 23:37 ` Paul Eggert
0 siblings, 1 reply; 6+ messages in thread
From: Lars Ingebrigtsen @ 2016-05-25 18:34 UTC (permalink / raw
To: Paul Eggert; +Cc: Magnus Henoch, emacs-devel
Paul Eggert <eggert@cs.ucla.edu> writes:
> Would it make sense to do this as a gnulib module for getaddrinfo_a?
> That way, Emacs could just continue to use the GNU/Linux interface,
> and other programs needing async DNS could do so as well, without too
> much trouble.
It that were possible, it would be much cleaner (and as you say, more
generally useful).
Implementing the full getaddrinfo_a interface would be a bigger job than
just the simple bits that Emacs uses (signal handling, etc), though.
--
(domestic pets only, the antidote for overdose, milk.)
bloggy blog: http://lars.ingebrigtsen.no
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: Async DNS for OSX
2016-05-25 18:34 ` Lars Ingebrigtsen
@ 2016-05-25 23:37 ` Paul Eggert
2016-05-26 10:46 ` Lars Ingebrigtsen
0 siblings, 1 reply; 6+ messages in thread
From: Paul Eggert @ 2016-05-25 23:37 UTC (permalink / raw
To: Lars Ingebrigtsen; +Cc: Magnus Henoch, emacs-devel
On 05/25/2016 11:34 AM, Lars Ingebrigtsen wrote:
> Implementing the full getaddrinfo_a interface would be a bigger job than
> just the simple bits that Emacs uses (signal handling, etc), though.
If the Gnulib module supports just the part of getaddrinfo_a that Emacs
needs, that should be good enough. Lots of Gnulib modules implement only
the "important" parts of the portable interface, where "important" means
"needed by GNU apps that use Gnulib", a definition that can change with
time as usage grows.
If all this is too much trouble, then just patching Emacs is OK too. We
can worry about libraryizing a getaddrinfo_a substitute later.
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: Async DNS for OSX
2016-05-25 23:37 ` Paul Eggert
@ 2016-05-26 10:46 ` Lars Ingebrigtsen
2016-05-26 14:41 ` Paul Eggert
0 siblings, 1 reply; 6+ messages in thread
From: Lars Ingebrigtsen @ 2016-05-26 10:46 UTC (permalink / raw
To: Paul Eggert; +Cc: Magnus Henoch, emacs-devel
Paul Eggert <eggert@cs.ucla.edu> writes:
> If the Gnulib module supports just the part of getaddrinfo_a that
> Emacs needs, that should be good enough. Lots of Gnulib modules
> implement only the "important" parts of the portable interface, where
> "important" means "needed by GNU apps that use Gnulib", a definition
> that can change with time as usage grows.
Cool.
> If all this is too much trouble, then just patching Emacs is OK
> too. We can worry about libraryizing a getaddrinfo_a substitute later.
My guess is that it would be easier for people to implement a partial
getaddrinfo_a in a Gnulib context than to implement async DNS in an
Emacs context. I think. Does Gnulib have getaddrinfo_a scaffolding
done? (I'm assuming there must be something of the kind per function it
implements.) Perhaps if that were in place (and included in Emacs),
then it would be even simpler for people to implement the missing OS
X/Windows bits?
--
(domestic pets only, the antidote for overdose, milk.)
bloggy blog: http://lars.ingebrigtsen.no
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: Async DNS for OSX
2016-05-26 10:46 ` Lars Ingebrigtsen
@ 2016-05-26 14:41 ` Paul Eggert
0 siblings, 0 replies; 6+ messages in thread
From: Paul Eggert @ 2016-05-26 14:41 UTC (permalink / raw
To: Lars Ingebrigtsen; +Cc: Magnus Henoch, emacs-devel
On 05/26/2016 03:46 AM, Lars Ingebrigtsen wrote:
> Does Gnulib have getaddrinfo_a scaffolding
> done? (I'm assuming there must be something of the kind per function it
> implements.)
No, unfortunately.
> Perhaps if that were in place (and included in Emacs),
> then it would be even simpler for people to implement the missing OS
> X/Windows bits?
Yes, that's the idea.
^ permalink raw reply [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.