>From f3b8731058961a529051c207b51f5f583cf3e790 Mon Sep 17 00:00:00 2001
From: Mathias Hasselmann <mathias@openismus.com>
Date: Tue, 27 Nov 2007 10:56:01 +0100
Subject: [PATCH] Announce postmaster's listen sockets via Avahi.

This patch adds serveral data structures and functions to announce
each of postmaster's listen sockets via Avahi.

Signed-off-by: Mathias Hasselmann <mathias@openismus.com>
---
 src/backend/postmaster/postmaster.c |  312 ++++++++++++++++++++++++++++++++++-
 1 files changed, 310 insertions(+), 2 deletions(-)

diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index e4e5bda..7482dc0 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -88,6 +88,13 @@
 #include <getopt.h>
 #endif
 
+#ifdef USE_AVAHI
+#include <avahi-client/publish.h>
+#include <avahi-common/alternative.h>
+#include <avahi-common/error.h>
+#include <avahi-common/thread-watch.h>
+#endif
+
 #ifdef USE_BONJOUR
 #include <DNSServiceDiscovery/DNSServiceDiscovery.h>
 #endif
@@ -207,6 +214,22 @@ bool		Db_user_namespace = false;
 
 char		*zeroconf_name;
 
+#if USE_AVAHI
+
+#define PM_SERVICE_TYPE				"_postgresql._tcp"
+
+typedef struct
+{
+	AvahiEntryGroup		*group;
+	char			*name;
+} ServiceState;
+
+static AvahiClient		*pmAvahiClient = NULL;
+static AvahiThreadedPoll	*pmAvahiPollApi = NULL;
+static ServiceState		 pmServices[MAXLISTEN];
+
+#endif
+
 /* PIDs of special child processes; 0 when not running */
 static pid_t StartupPID = 0,
 			BgWriterPID = 0,
@@ -290,10 +313,16 @@ extern int	optreset;
  */
 static void checkDataDir(void);
 
+#ifdef USE_AVAHI
+static void HandleAvahiStates(AvahiClient *client, AvahiClientState state, void *data);
+static void PublishServices(ServiceState *service);
+static void ExitAvahiClient(void);
+#endif
+
 #ifdef USE_BONJOUR
-static void reg_reply(DNSServiceRegistrationReplyErrorType errorCode,
-		  void *context);
+static void reg_reply(DNSServiceRegistrationReplyErrorType errorCode, void *context);
 #endif
+
 static void pmdaemonize(void);
 static Port *ConnCreate(int serverFd);
 static void ConnFree(Port *port);
@@ -848,6 +877,33 @@ PostmasterMain(int argc, char *argv[])
 		pfree(rawstring);
 	}
 
+#ifdef USE_AVAHI
+	/* Register for Avahi only if we opened TCP socket(s) */
+	if (ListenSocket[0] != -1 && zeroconf_name != NULL)
+	{
+		int rc = AVAHI_OK;
+
+		/* Use Avahi's threaded polling API since Postgresql doesn't use
+		 * any of the directly supported plugable event loops (GLib, Qt). */
+		pmAvahiPollApi = avahi_threaded_poll_new();
+		pmAvahiClient = avahi_client_new(avahi_threaded_poll_get(pmAvahiPollApi),
+						 AVAHI_CLIENT_NO_FAIL, HandleAvahiStates,
+						 NULL, &rc);
+
+		if (pmAvahiClient)
+		{
+			/* Bring the threaded polling API into action. */
+			avahi_threaded_poll_start(pmAvahiPollApi);
+		}
+		else
+		{
+			ereport(WARNING,
+					(errmsg("could not create Avahi client: %s",
+						avahi_strerror (rc))));
+		}
+	}
+#endif
+
 #ifdef USE_BONJOUR
 	/* Register for Bonjour only if we opened TCP socket(s) */
 	if (ListenSocket[0] != -1 && zeroconf_name != NULL)
@@ -3741,6 +3797,10 @@ SubPostmasterMain(int argc, char *argv[])
 static void
 ExitPostmaster(int status)
 {
+#ifdef USE_AVAHI
+	ExitAvahiClient();
+#endif
+
 	/* should cleanup shared memory and kill all backends */
 
 	/*
@@ -4561,3 +4621,251 @@ pgwin32_deadchild_callback(PVOID lpParameter, BOOLEAN TimerOrWaitFired)
 }
 
 #endif   /* WIN32 */
+
+#ifdef USE_AVAHI
+
+static void
+HandleServiceCollision(ServiceState *service, bool local)
+{
+	char *new_name = avahi_alternative_service_name (service->name);
+
+	ereport(WARNING,
+			(errmsg("%s service name collision for \"%s\", renaming to \"%s\"",
+			 	local ? "local" : "remote", service->name, new_name)));
+
+	free(service->name);
+	service->name = new_name;
+
+	avahi_entry_group_reset (service->group);
+	PublishServices(service);
+}
+
+static void
+PublishServices(ServiceState *service)
+{
+	int socket_index = (service - pmServices);
+
+	struct sockaddr_storage addr;
+	socklen_t addrlen = sizeof addr;
+	AvahiProtocol protocol;
+
+	char host[NI_MAXHOST];
+	const char *fqdn = NULL;
+	int port = PostPortNumber;
+	int rc;
+
+	/* Find the host name the socket is associated with. */
+
+	if (-1 == ListenSocket[socket_index] ||
+	    -1 == getsockname(ListenSocket[socket_index], (struct sockaddr*) &addr, &addrlen))
+		return;
+
+	if (getnameinfo((struct sockaddr*) &addr, addrlen,
+			host, sizeof host, NULL, 0, NI_NAMEREQD))
+	{
+		/* Seems no host name or address is associated -
+		 * pretty print the host name used in logging. */
+		strncpy(host, "*", 2);
+	}
+	else if (strcmp (host, "localhost"))
+	{
+		/* Currently Avahi doesn't accept "localhost" as host name.
+		 * Therefore leave fqdn at NULL in that case - even if it
+		 * means announcing the service as global service for the
+		 * official host name of this machine. */
+		fqdn = host;
+	}
+
+	/* Extract the port number from the socket's address. */
+
+	protocol = avahi_af_to_proto(addr.ss_family);
+
+	if (AVAHI_PROTO_UNSPEC == protocol)
+		return;
+
+	if (0 == port)
+	{
+		switch (protocol)
+		{
+			case AVAHI_PROTO_INET:
+				port = htons (((struct sockaddr_in*) &addr)->sin_port);
+				break;
+
+			case AVAHI_PROTO_INET6:
+				port = htons (((struct sockaddr_in6*) &addr)->sin6_port);
+				break;
+
+			default:
+				return;
+		}
+	}
+
+	/* Build the human friendly name used to announce the service.
+	 * Use official host name, when the 'zeroconf_name' option is empty. */
+
+	if (NULL == service->name)
+		service->name = strdup (*zeroconf_name ? zeroconf_name :
+					avahi_client_get_host_name_fqdn(pmAvahiClient));
+
+	/* Finally announce the _postgresql._tcp service. */
+
+	rc = avahi_entry_group_add_service(service->group,
+					   AVAHI_IF_UNSPEC, protocol, 0,
+					   service->name, PM_SERVICE_TYPE,
+					   NULL, fqdn, port, NULL);
+
+	if (AVAHI_OK == rc)
+		ereport(LOG,
+				(errmsg("announcing %s socket %s:%d as \"%s\"",
+					avahi_proto_to_string (protocol),
+					host, port, service->name)));
+	else if (AVAHI_ERR_COLLISION == rc)
+		/* Find another service name, if the choosen name is already in use. */
+		HandleServiceCollision(service, true);
+	else
+		ereport(WARNING,
+				(errmsg("could not announce %s socket %s:%d as \"%s\": %s",
+					avahi_proto_to_string (protocol), host, port,
+					service->name, avahi_strerror(rc))));
+
+	/* Finally publish the service announcement. */
+	avahi_entry_group_commit (service->group);
+}
+
+static void
+HandleAvahiEntryStates(AvahiEntryGroup *group, AvahiEntryGroupState state, void *data)
+{
+	AvahiClient *client = avahi_entry_group_get_client (group);
+	ServiceState *service = data;
+
+	service->group = group;
+
+	switch (state)
+	{
+		case AVAHI_ENTRY_GROUP_REGISTERING:
+		case AVAHI_ENTRY_GROUP_ESTABLISHED:
+			break;
+
+		case AVAHI_ENTRY_GROUP_UNCOMMITED:
+			PublishServices(service);
+			break;
+
+		case AVAHI_ENTRY_GROUP_COLLISION:
+			HandleServiceCollision(service, false);
+			break;
+
+		case AVAHI_ENTRY_GROUP_FAILURE:
+			ereport(ERROR,
+					(errmsg("Avahi entry group error: %s",
+					 avahi_strerror(avahi_client_errno(client)))));
+			break;
+	}
+}
+
+static void
+HandleAvahiStates(AvahiClient *client, AvahiClientState state, void *data)
+{
+	int i;
+
+	pmAvahiClient = client;
+
+	switch (state)
+	{
+		case AVAHI_CLIENT_S_RUNNING:
+			/* The Avahi client found its daemon. Announce our services. */
+
+			for (i = 0; i < MAXLISTEN; ++i)
+				if (-1 != ListenSocket[i])
+					avahi_entry_group_new(client, HandleAvahiEntryStates, &pmServices[i]);
+
+			break;
+
+		case AVAHI_CLIENT_S_REGISTERING:
+			/* The Avahi client was suspended. Revoke all announcements. */
+
+			ereport(LOG,
+					(errmsg("resetting services publications")));
+
+			for (i = 0; i < MAXLISTEN; ++i)
+				if (pmServices[i].group)
+					avahi_entry_group_reset (pmServices[i].group);
+
+			break;
+
+		case AVAHI_CLIENT_S_COLLISION:
+			/* A name collision was detected. Find new service names. */
+
+			for (i = 0; i < MAXLISTEN; ++i)
+				if (pmServices[i].group)
+					HandleServiceCollision(&pmServices[i], false);
+
+			break;
+
+		case AVAHI_CLIENT_FAILURE:
+			/* Some dramatic error occured. Just report it, do not try to recover. */
+
+			ereport(ERROR,
+					(errmsg("Avahi client error: %s",
+					 avahi_strerror(avahi_client_errno(client)))));
+			break;
+
+		case AVAHI_CLIENT_CONNECTING:
+			/* The Avahi client is setup, but hasn't found an Avahi daemon yet.
+ 			 * Reports this and wait for better days. */
+
+			ereport(WARNING,
+					(errmsg("waiting for Avahi daemon to startup")));
+			break;
+	}
+}
+
+static void
+ExitAvahiClient(void)
+{
+	int i;
+
+	/* As we use the threaded polling API, all state changes and
+	 * announcement handling happens in the polling thread. Therefore
+	 * the main thread must acquire the pollling API lock before
+	 * cleaning up. */
+
+	if (pmAvahiPollApi)
+		avahi_threaded_poll_lock(pmAvahiPollApi);
+
+	/* Revoke and release all service announcements. */
+
+	for (i = 0; i < MAXLISTEN; ++i)
+	{
+		if (pmServices[i].group)
+		{
+			avahi_entry_group_free(pmServices[i].group);
+			pmServices[i].group = NULL;
+		}
+
+		if (pmServices[i].name)
+		{
+			free(pmServices[i].name);
+			pmServices[i].name = NULL;
+		}
+	}
+
+	/* Also destroy the Avahi client. */
+
+	if (pmAvahiClient)
+	{
+		avahi_client_free(pmAvahiClient);
+		pmAvahiClient = NULL;
+	}
+
+	/* Finally tear down the polling API. */
+
+	if (pmAvahiPollApi)
+	{
+		avahi_threaded_poll_unlock(pmAvahiPollApi);
+		avahi_threaded_poll_stop(pmAvahiPollApi);
+		avahi_threaded_poll_free(pmAvahiPollApi);
+		pmAvahiPollApi = NULL;
+	}
+}
+
+#endif   /* USE_AVAHI */
-- 
1.5.2.5

