/* * linc-server.c: This file is part of the linc library. * * Authors: * Elliot Lee (sopwith@redhat.com) * Michael Meeks (michael@ximian.com) * Mark McLouglin (mark@skynet.ie) & others * * Copyright 2001, Red Hat, Inc., Ximian, Inc., * Sun Microsystems, Inc. */ #include #include #include #include #include #include #include #include #include "linc-private.h" #include "linc-compat.h" enum { NEW_CONNECTION, LAST_SIGNAL }; static guint server_signals [LAST_SIGNAL] = { 0 }; static GList *server_list = NULL; static GObjectClass *parent_class = NULL; static void my_cclosure_marshal_VOID__OBJECT (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data) { typedef void (*GSignalFunc_VOID__OBJECT) (gpointer data1, GObject *arg_1, gpointer data2); register GSignalFunc_VOID__OBJECT callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values >= 2); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GSignalFunc_VOID__OBJECT) ( marshal_data ? marshal_data : cc->callback); callback (data1, g_value_peek_pointer (param_values + 1), data2); } static void link_server_init (LinkServer *srv) { srv->priv = g_new0 (LinkServerPrivate, 1); srv->priv->fd = -1; } static void link_server_client_connection_broken (LinkConnection *connection, gpointer user_data) { LinkServer *server = user_data; server->priv->connections = g_slist_remove (server->priv->connections, connection); link_connection_unref (connection); } static void link_server_dispose (GObject *obj) { GSList *l; LinkServer *srv = (LinkServer *) obj; server_list = g_list_remove (server_list, srv); d_printf ("Dispose / close server fd %d\n", srv->priv->fd); if (srv->priv->tag) { LinkWatch *thewatch = srv->priv->tag; srv->priv->tag = NULL; link_io_remove_watch (thewatch); } link_protocol_destroy_cnx (srv->proto, srv->priv->fd, srv->local_host_info, srv->local_serv_info); srv->priv->fd = -1; while ((l = srv->priv->connections)) { GObject *o = l->data; g_signal_handlers_disconnect_by_func (o, link_server_client_connection_broken, srv); srv->priv->connections = l->next; g_slist_free_1 (l); link_connection_unref (o); } parent_class->dispose (obj); } static void link_server_finalize (GObject *obj) { LinkServer *srv = (LinkServer *)obj; g_free (srv->local_host_info); g_free (srv->local_serv_info); g_free (srv->priv); parent_class->finalize (obj); } static LinkConnection * link_server_create_connection (LinkServer *srv) { return g_object_new (link_connection_get_type (), NULL); } static gboolean link_server_accept_connection (LinkServer *server, LinkConnection **connection) { LinkServerClass *klass; struct sockaddr *saddr; int addrlen, fd; g_return_val_if_fail (connection != NULL, FALSE); *connection = NULL; addrlen = server->proto->addr_len; saddr = g_alloca (addrlen); LINK_TEMP_FAILURE_RETRY_SOCKET (accept (server->priv->fd, saddr, &addrlen), fd); #ifdef HAVE_WINSOCK2_H if (fd == INVALID_SOCKET) { fd = -1; link_map_winsock_error_to_errno (); } #endif if (fd < 0) { d_printf ("accept on %d failed: %s\n", server->priv->fd, link_strerror (errno)); return FALSE; } if (server->create_options & LINK_CONNECTION_LOCAL_ONLY && !link_protocol_is_local (server->proto, saddr, addrlen)) { d_printf ("link_server_accept_connection: not local, closing %d\n", fd); LINK_CLOSE_SOCKET (fd); return FALSE; } if (server->create_options & LINK_CONNECTION_NONBLOCKING) { #ifdef HAVE_WINSOCK2_H u_long yes = 1; if (ioctlsocket (fd, FIONBIO, &yes) == SOCKET_ERROR) { link_map_winsock_error_to_errno (); d_printf ("failed to set FIONBIO on %d: %s\n", fd, link_strerror (errno)); d_printf ("closing %d\n", fd); LINK_CLOSE_SOCKET (fd); return FALSE; } #else if (fcntl (fd, F_SETFL, O_NONBLOCK) < 0) { d_printf ("failed to set O_NONBLOCK on %d: %s\n", fd, link_strerror (errno)); d_printf ("closing %d\n", fd); LINK_CLOSE_SOCKET (fd); return FALSE; } #endif } #if defined (F_SETFD) && defined (FD_CLOEXEC) if (fcntl (fd, F_SETFD, FD_CLOEXEC) < 0) { d_printf ("failed to set cloexec on %d: %s\n", fd, link_strerror (errno)); d_printf ("closing %d\n", fd); LINK_CLOSE_SOCKET (fd); return FALSE; } #endif #ifdef HAVE_WINSOCK2_H { SOCKET newfd; if (!DuplicateHandle (GetCurrentProcess (), (HANDLE) fd, GetCurrentProcess (), (LPHANDLE) &newfd, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { d_printf ("DuplicateHandle failed: %s\n", link_strerror (WSAGetLastError ())); return FALSE; } fd = newfd; } #endif klass = (LinkServerClass *) G_OBJECT_GET_CLASS (server); g_assert (klass->create_connection); *connection = klass->create_connection (server); g_return_val_if_fail (*connection != NULL, FALSE); d_printf ("accepted a new connection (%d) on server %d\n", fd, server->priv->fd); link_connection_from_fd (*connection, fd, server->proto, NULL, NULL, FALSE, LINK_CONNECTED, server->create_options); g_signal_connect (*connection, "broken", G_CALLBACK (link_server_client_connection_broken), server); server->priv->connections = g_slist_prepend ( server->priv->connections, *connection); return TRUE; } static gboolean link_server_handle_io (GIOChannel *gioc, GIOCondition condition, gpointer data) { gboolean accepted; LinkServer *server = data; LinkConnection *connection = NULL; if (!(condition & LINK_IN_CONDS)) { /* * This call to g_warning was changed from g_error to avoid * a program crash. See bug #126209. */ d_printf ("error condition on server fd is %#x\n", condition); return TRUE; } accepted = link_server_accept_connection (server, &connection); if (!accepted) { GValue parms[2]; memset (parms, 0, sizeof (parms)); g_value_init (parms, G_OBJECT_TYPE (server)); g_value_set_object (parms, G_OBJECT (server)); g_value_init (parms + 1, G_TYPE_OBJECT); /* FIXME: this connection is always NULL */ g_value_set_object (parms + 1, (GObject *) connection); d_printf ("p %d, Non-accepted input on fd %d\n", getpid (), server->priv->fd); g_signal_emitv (parms, server_signals [NEW_CONNECTION], 0, NULL); g_value_unset (parms); g_value_unset (parms + 1); } return TRUE; } /** * link_server_setup: * @srv: the connection to setup * @proto_name: the protocol to use * @local_host_info: the local hsot * @local_serv_info: remote server info * @create_options: various create options * * Setup the server object. You should create a server object * via g_object_new and then set it up, using this method. * * Return value: the initialized server **/ gboolean link_server_setup (LinkServer *srv, const char *proto_name, const char *local_host_info, const char *local_serv_info, LinkConnectionOptions create_options) { const LinkProtocolInfo *proto; int fd, n; struct sockaddr *saddr; LinkSockLen saddr_len; const char *local_host; char *service, *hostname; #ifndef LINK_SSL_SUPPORT if (create_options & LINK_CONNECTION_SSL) return FALSE; #endif proto = link_protocol_find (proto_name); if (!proto) { d_printf ("Can't find proto '%s'\n", proto_name); return FALSE; } srv->proto = proto; if (local_host_info) local_host = local_host_info; else local_host = link_get_local_hostname (); address_in_use: saddr = link_protocol_get_sockaddr ( proto, local_host, local_serv_info, &saddr_len); if (!saddr) { d_printf ("Can't get_sockaddr proto '%s' '%s'\n", local_host, local_serv_info ? local_serv_info : "(null)"); return FALSE; } fd = socket (proto->family, SOCK_STREAM, proto->stream_proto_num); #ifdef HAVE_WINSOCK2_H if (fd == INVALID_SOCKET) { fd = -1; link_map_winsock_error_to_errno (); } #endif if (fd < 0) { g_free (saddr); d_printf ("socket (%d, SOCK_STREAM, %d) failed: %s\n", proto->family, proto->stream_proto_num, link_strerror (errno)); return FALSE; } #ifdef HAVE_WINSOCK2_H { SOCKET newfd; if (!DuplicateHandle (GetCurrentProcess (), (HANDLE) fd, GetCurrentProcess (), (LPHANDLE) &newfd, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { d_printf ("DuplicateHandle failed: %s\n", link_strerror (WSAGetLastError ())); return FALSE; } fd = newfd; } #endif { static const int oneval = 1; setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &oneval, sizeof (oneval)); } n = 0; errno = 0; if ((proto->flags & LINK_PROTOCOL_NEEDS_BIND) || local_serv_info) n = bind (fd, saddr, saddr_len); #ifdef HAVE_WINSOCK2_H if (n == SOCKET_ERROR) { n = -1; link_map_winsock_error_to_errno (); } #endif if (n && errno == EADDRINUSE) { LINK_CLOSE_SOCKET(fd); if (!local_serv_info) { d_printf ("bind failed; retrying\n"); goto address_in_use; } } if (n) d_printf ("bind failed: %s\n", link_strerror (errno)); if (!n) { n = listen (fd, 10); #ifdef HAVE_WINSOCK2_H if (n == SOCKET_ERROR) { n = -1; link_map_winsock_error_to_errno (); } #endif if (n) d_printf ("listen failed: %s\n", link_strerror (errno)); } if (!n && create_options & LINK_CONNECTION_NONBLOCKING) { #ifdef HAVE_WINSOCK2_H u_long yes = 1; if (ioctlsocket (fd, FIONBIO, &yes) == SOCKET_ERROR) { n = -1; link_map_winsock_error_to_errno (); } #else n = fcntl (fd, F_SETFL, O_NONBLOCK); #endif if (n) d_printf ("failed to set nonblock on %d: %s\n", fd, link_strerror (errno)); } #if defined (F_SETFD) && defined (FD_CLOEXEC) if (!n) { n = fcntl (fd, F_SETFD, FD_CLOEXEC); if (n) d_printf ("failed to set cloexec on %d: %s", fd, link_strerror (errno)); } #endif if (!n) { n = getsockname (fd, saddr, &saddr_len); #ifdef HAVE_WINSOCK2_H if (n == SOCKET_ERROR) { n = -1; link_map_winsock_error_to_errno (); } #endif if (n) { link_protocol_destroy_addr (proto, fd, saddr); d_printf ("getsockname failed: %s\n", link_strerror (errno)); return FALSE; } } if (!link_protocol_get_sockinfo (proto, saddr, &hostname, &service)) { link_protocol_destroy_addr (proto, fd, saddr); d_printf ("link_getsockinfo failed.\n"); return FALSE; } g_free (saddr); srv->priv->fd = fd; if (create_options & LINK_CONNECTION_NONBLOCKING) { g_assert (srv->priv->tag == NULL); srv->priv->tag = link_io_add_watch_fd ( fd, LINK_IN_CONDS | LINK_ERR_CONDS, link_server_handle_io, srv); } srv->create_options = create_options; if (local_host_info) { g_free (hostname); srv->local_host_info = g_strdup (local_host_info); } else srv->local_host_info = hostname; srv->local_serv_info = service; server_list = g_list_prepend (server_list, srv); d_printf ("Created a new server fd (%d) '%s', '%s', '%s'\n", fd, proto->name, hostname ? hostname : "", service ? service : ""); return TRUE; } static void link_server_class_init (LinkServerClass *klass) { GType ptype; GClosure *closure; GObjectClass *object_class = (GObjectClass *) klass; object_class->dispose = link_server_dispose; object_class->finalize = link_server_finalize; klass->create_connection = link_server_create_connection; parent_class = g_type_class_peek_parent (klass); closure = g_signal_type_cclosure_new ( G_OBJECT_CLASS_TYPE (klass), G_STRUCT_OFFSET (LinkServerClass, new_connection)); ptype = G_TYPE_OBJECT; server_signals [NEW_CONNECTION] = g_signal_newv ( "new_connection", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST, closure, NULL, NULL, my_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, &ptype); } GType link_server_get_type (void) { static GType object_type = 0; if (!object_type) { static const GTypeInfo object_info = { sizeof (LinkServerClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) link_server_class_init, NULL, /* class_finalize */ NULL, /* class_data */ sizeof (LinkServer), 0, /* n_preallocs */ (GInstanceInitFunc) link_server_init }; object_type = g_type_register_static ( G_TYPE_OBJECT, "LinkServer", &object_info, 0); } return object_type; } void link_servers_move_io_T (gboolean to_io_thread) { GList *l; for (l = server_list; l; l = l->next) { LinkServer *srv = l->data; link_watch_move_io (srv->priv->tag, to_io_thread); } }