Use g_simple_async_result_{new_,}take_error
[platform/upstream/glib.git] / gio / gsocketlistener.c
index 522ed44..503fa31 100644 (file)
@@ -38,7 +38,6 @@
 #include <gio/ginetsocketaddress.h>
 #include "glibintl.h"
 
-#include "gioalias.h"
 
 /**
  * SECTION: gsocketlistener
@@ -234,12 +233,17 @@ g_socket_listener_add_socket (GSocketListener  *listener,
       return FALSE;
     }
 
+  g_object_ref (socket);
   g_ptr_array_add (listener->priv->sockets, socket);
 
   if (source_object)
     g_object_set_qdata_full (G_OBJECT (socket), source_quark,
                             g_object_ref (source_object), g_object_unref);
 
+
+  if (G_SOCKET_LISTENER_GET_CLASS (listener)->changed)
+    G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener);
+
   return TRUE;
 }
 
@@ -250,7 +254,7 @@ g_socket_listener_add_socket (GSocketListener  *listener,
  * @type: a #GSocketType
  * @protocol: a #GSocketProtocol
  * @source_object: Optional #GObject identifying this source
- * @effective_address: location to store the address that was bound to, or %NULL.
+ * @effective_address: (out) (allow-none): location to store the address that was bound to, or %NULL.
  * @error: #GError for error reporting, or %NULL to ignore.
  *
  * Creates a socket of type @type and protocol @protocol, binds
@@ -301,15 +305,13 @@ g_socket_listener_add_address (GSocketListener  *listener,
   g_socket_set_listen_backlog (socket, listener->priv->listen_backlog);
 
   if (!g_socket_bind (socket, address, TRUE, error) ||
-      !g_socket_listen (socket, error) ||
-      !g_socket_listener_add_socket (listener, socket,
-                                    source_object,
-                                    error))
+      !g_socket_listen (socket, error))
     {
       g_object_unref (socket);
       return FALSE;
     }
 
+  local_address = NULL;
   if (effective_address)
     {
       local_address = g_socket_get_local_address (socket, error);
@@ -318,11 +320,22 @@ g_socket_listener_add_address (GSocketListener  *listener,
          g_object_unref (socket);
          return FALSE;
        }
-      *effective_address = local_address;
     }
 
-  if (G_SOCKET_LISTENER_GET_CLASS (listener)->changed)
-    G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener);
+  if (!g_socket_listener_add_socket (listener, socket,
+                                    source_object,
+                                    error))
+    {
+      if (local_address)
+       g_object_unref (local_address);
+      g_object_unref (socket);
+      return FALSE;
+    }
+
+  if (effective_address)
+    *effective_address = local_address;
+
+  g_object_unref (socket); /* add_socket refs this */
 
   return TRUE;
 }
@@ -549,7 +562,7 @@ accept_callback (GSocket      *socket,
 /**
  * g_socket_listener_accept_socket:
  * @listener: a #GSocketListener
- * @source_object: location where #GObject pointer will be stored, or %NULL
+ * @source_object: (out) (transfer none) (allow-none): location where #GObject pointer will be stored, or %NULL.
  * @cancellable: optional #GCancellable object, %NULL to ignore.
  * @error: #GError for error reporting, or %NULL to ignore.
  *
@@ -568,7 +581,7 @@ accept_callback (GSocket      *socket,
  * triggering the cancellable object from another thread. If the operation
  * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
  *
- * Returns: a #GSocket on success, %NULL on error.
+ * Returns: (transfer full): a #GSocket on success, %NULL on error.
  *
  * Since: 2.22
  */
@@ -614,7 +627,7 @@ g_socket_listener_accept_socket (GSocketListener  *listener,
       g_main_loop_unref (loop);
     }
 
-  if (!(socket = g_socket_accept (accept_socket, error)))
+  if (!(socket = g_socket_accept (accept_socket, cancellable, error)))
     return NULL;
 
   if (source_object)
@@ -626,7 +639,7 @@ g_socket_listener_accept_socket (GSocketListener  *listener,
 /**
  * g_socket_listener_accept:
  * @listener: a #GSocketListener
- * @source_object: location where #GObject pointer will be stored, or %NULL
+ * @source_object: (out) (transfer none) (allow-none): location where #GObject pointer will be stored, or %NULL
  * @cancellable: optional #GCancellable object, %NULL to ignore.
  * @error: #GError for error reporting, or %NULL to ignore.
  *
@@ -642,7 +655,7 @@ g_socket_listener_accept_socket (GSocketListener  *listener,
  * triggering the cancellable object from another thread. If the operation
  * was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
  *
- * Returns: a #GSocketConnection on success, %NULL on error.
+ * Returns: (transfer full): a #GSocketConnection on success, %NULL on error.
  *
  * Since: 2.22
  */
@@ -681,30 +694,23 @@ accept_ready (GSocket      *accept_socket,
 {
   struct AcceptAsyncData *data = _data;
   GError *error = NULL;
+  GSocket *socket;
+  GObject *source_object;
 
-  if (!g_cancellable_set_error_if_cancelled (data->cancellable,
-                                             &error))
+  socket = g_socket_accept (accept_socket, data->cancellable, &error);
+  if (socket)
     {
-      GSocket *socket;
-      GObject *source_object;
-
-      socket = g_socket_accept (accept_socket, &error);
-      if (socket)
-       {
-         g_simple_async_result_set_op_res_gpointer (data->simple, socket,
-                                                    g_object_unref);
-         source_object = g_object_get_qdata (G_OBJECT (accept_socket), source_quark);
-         if (source_object)
-           g_object_set_qdata_full (G_OBJECT (data->simple),
-                                    source_quark,
-                                    g_object_ref (source_object), g_object_unref);
-       }
+      g_simple_async_result_set_op_res_gpointer (data->simple, socket,
+                                                g_object_unref);
+      source_object = g_object_get_qdata (G_OBJECT (accept_socket), source_quark);
+      if (source_object)
+       g_object_set_qdata_full (G_OBJECT (data->simple),
+                                source_quark,
+                                g_object_ref (source_object), g_object_unref);
     }
-
-  if (error)
+  else
     {
-      g_simple_async_result_set_from_error (data->simple, error);
-      g_error_free (error);
+      g_simple_async_result_take_error (data->simple, error);
     }
 
   g_simple_async_result_complete_in_idle (data->simple);
@@ -757,7 +763,7 @@ g_socket_listener_accept_socket_async (GSocketListener     *listener,
                               accept_ready,
                               data,
                               cancellable,
-                              NULL);
+                              g_main_context_get_thread_default ());
 }
 
 /**
@@ -770,7 +776,7 @@ g_socket_listener_accept_socket_async (GSocketListener     *listener,
  *
  * Finishes an async accept operation. See g_socket_listener_accept_socket_async()
  *
- * Returns: a #GSocket on success, %NULL on error.
+ * Returns: (transfer full): a #GSocket on success, %NULL on error.
  *
  * Since: 2.22
  */
@@ -783,7 +789,7 @@ g_socket_listener_accept_socket_finish (GSocketListener  *listener,
   GSocket *socket;
   GSimpleAsyncResult *simple;
 
-  g_return_val_if_fail (G_IS_SOCKET_LISTENER (listener), FALSE);
+  g_return_val_if_fail (G_IS_SOCKET_LISTENER (listener), NULL);
 
   simple = G_SIMPLE_ASYNC_RESULT (result);
 
@@ -837,7 +843,7 @@ g_socket_listener_accept_async (GSocketListener     *listener,
  *
  * Finishes an async accept operation. See g_socket_listener_accept_async()
  *
- * Returns: a #GSocketConnection on success, %NULL on error.
+ * Returns: (transfer full): a #GSocketConnection on success, %NULL on error.
  *
  * Since: 2.22
  */
@@ -919,5 +925,227 @@ g_socket_listener_close (GSocketListener *listener)
   listener->priv->closed = TRUE;
 }
 
-#define __G_SOCKET_LISTENER_C__
-#include "gioaliasdef.c"
+/**
+ * g_socket_listener_add_any_inet_port:
+ * @listener: a #GSocketListener
+ * @source_object: Optional #GObject identifying this source
+ * @error: a #GError location to store the error occuring, or %NULL to
+ * ignore.
+ *
+ * Listens for TCP connections on any available port number for both
+ * IPv6 and IPv4 (if each are available).
+ *
+ * This is useful if you need to have a socket for incoming connections
+ * but don't care about the specific port number.
+ *
+ * @source_object will be passed out in the various calls
+ * to accept to identify this particular source, which is
+ * useful if you're listening on multiple addresses and do
+ * different things depending on what address is connected to.
+ *
+ * Returns: the port number, or 0 in case of failure.
+ *
+ * Since: 2.24
+ **/
+guint16
+g_socket_listener_add_any_inet_port (GSocketListener  *listener,
+                                    GObject          *source_object,
+                                     GError          **error)
+{
+  GSList *sockets_to_close = NULL;
+  guint16 candidate_port = 0;
+  GSocket *socket6 = NULL;
+  GSocket *socket4 = NULL;
+  gint attempts = 37;
+
+  /*
+   * multi-step process:
+   *  - first, create an IPv6 socket.
+   *  - if that fails, create an IPv4 socket and bind it to port 0 and
+   *    that's it.  no retries if that fails (why would it?).
+   *  - if our IPv6 socket also speaks IPv4 then we are done.
+   *  - if not, then we need to create a IPv4 socket with the same port
+   *    number.  this might fail, of course.  so we try this a bunch of
+   *    times -- leaving the old IPv6 sockets open so that we get a
+   *    different port number to try each time.
+   *  - if all that fails then just give up.
+   */
+
+  while (attempts--)
+    {
+      GInetAddress *inet_address;
+      GSocketAddress *address;
+      gboolean result;
+
+      g_assert (socket6 == NULL);
+      socket6 = g_socket_new (G_SOCKET_FAMILY_IPV6,
+                              G_SOCKET_TYPE_STREAM,
+                              G_SOCKET_PROTOCOL_DEFAULT,
+                              NULL);
+
+      if (socket6 != NULL)
+        {
+          inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV6);
+          address = g_inet_socket_address_new (inet_address, 0);
+          g_object_unref (inet_address);
+          result = g_socket_bind (socket6, address, TRUE, error);
+          g_object_unref (address);
+
+          if (!result ||
+              !(address = g_socket_get_local_address (socket6, error)))
+            {
+              g_object_unref (socket6);
+              socket6 = NULL;
+              break;
+            }
+
+          g_assert (G_IS_INET_SOCKET_ADDRESS (address));
+          candidate_port =
+            g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address));
+          g_assert (candidate_port != 0);
+          g_object_unref (address);
+
+          if (g_socket_speaks_ipv4 (socket6))
+            break;
+        }
+
+      g_assert (socket4 == NULL);
+      socket4 = g_socket_new (G_SOCKET_FAMILY_IPV4,
+                              G_SOCKET_TYPE_STREAM,
+                              G_SOCKET_PROTOCOL_DEFAULT,
+                              socket6 ? NULL : error);
+
+      if (socket4 == NULL)
+        /* IPv4 not supported.
+         * if IPv6 is supported then candidate_port will be non-zero
+         *   (and the error parameter above will have been NULL)
+         * if IPv6 is unsupported then candidate_port will be zero
+         *   (and error will have been set by the above call)
+         */
+        break;
+
+      inet_address = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
+      address = g_inet_socket_address_new (inet_address, candidate_port);
+      g_object_unref (inet_address);
+      /* a note on the 'error' clause below:
+       *
+       * if candidate_port is 0 then we report the error right away
+       * since it is strange that this binding would fail at all.
+       * otherwise, we ignore the error message (ie: NULL).
+       *
+       * the exception to this rule is the last time through the loop
+       * (ie: attempts == 0) in which case we want to set the error
+       * because failure here means that the entire call will fail and
+       * we need something to show to the user.
+       *
+       * an english summary of the situation:  "if we gave a candidate
+       * port number AND we have more attempts to try, then ignore the
+       * error for now".
+       */
+      result = g_socket_bind (socket4, address, TRUE,
+                              (candidate_port && attempts) ? NULL : error);
+      g_object_unref (address);
+
+      if (candidate_port)
+        {
+          g_assert (socket6 != NULL);
+
+          if (result)
+            /* got our candidate port successfully */
+            break;
+
+          else
+            /* we failed to bind to the specified port.  try again. */
+            {
+              g_object_unref (socket4);
+              socket4 = NULL;
+
+              /* keep this open so we get a different port number */
+              sockets_to_close = g_slist_prepend (sockets_to_close,
+                                                  socket6);
+              candidate_port = 0;
+              socket6 = NULL;
+            }
+        }
+      else
+        /* we didn't tell it a port.  this means two things.
+         *  - if we failed, then something really bad happened.
+         *  - if we succeeded, then we need to find out the port number.
+         */
+        {
+          g_assert (socket6 == NULL);
+
+          if (!result ||
+              !(address = g_socket_get_local_address (socket4, error)))
+            {
+              g_object_unref (socket4);
+              socket4 = NULL;
+              break;
+            }
+
+            g_assert (G_IS_INET_SOCKET_ADDRESS (address));
+            candidate_port =
+              g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address));
+            g_assert (candidate_port != 0);
+            g_object_unref (address);
+            break;
+        }
+    }
+
+  /* should only be non-zero if we have a socket */
+  g_assert ((candidate_port != 0) == (socket4 || socket6));
+
+  while (sockets_to_close)
+    {
+      g_object_unref (sockets_to_close->data);
+      sockets_to_close = g_slist_delete_link (sockets_to_close,
+                                              sockets_to_close);
+    }
+
+  /* now we actually listen() the sockets and add them to the listener */
+  if (socket6 != NULL)
+    {
+      g_socket_set_listen_backlog (socket6, listener->priv->listen_backlog);
+      if (!g_socket_listen (socket6, error))
+        {
+          g_object_unref (socket6);
+          if (socket4)
+            g_object_unref (socket4);
+
+          return 0;
+        }
+
+      if (source_object)
+        g_object_set_qdata_full (G_OBJECT (socket6), source_quark,
+                                 g_object_ref (source_object),
+                                 g_object_unref);
+
+      g_ptr_array_add (listener->priv->sockets, socket6);
+    }
+
+   if (socket4 != NULL)
+    {
+      g_socket_set_listen_backlog (socket4, listener->priv->listen_backlog);
+      if (!g_socket_listen (socket4, error))
+        {
+          g_object_unref (socket4);
+          if (socket6)
+            g_object_unref (socket6);
+
+          return 0;
+        }
+
+      if (source_object)
+        g_object_set_qdata_full (G_OBJECT (socket4), source_quark,
+                                 g_object_ref (source_object),
+                                 g_object_unref);
+
+      g_ptr_array_add (listener->priv->sockets, socket4);
+    }
+
+  if ((socket4 != NULL || socket6 != NULL) &&
+      G_SOCKET_LISTENER_GET_CLASS (listener)->changed)
+    G_SOCKET_LISTENER_GET_CLASS (listener)->changed (listener);
+
+  return candidate_port;
+}