diff --git a/gnoemoe/gm-net.c b/gnoemoe/gm-net.c index 2339d00..ac5360d 100644 --- a/gnoemoe/gm-net.c +++ b/gnoemoe/gm-net.c @@ -1,4 +1,3 @@ - #include #include #include @@ -13,6 +12,8 @@ #include #include #include +#include +#include #include "gm-net.h" #include "gm-marshal.h" @@ -21,6 +22,20 @@ #define GM_NET_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GM_TYPE_NET, GmNetPrivate)) +typedef struct _threadinfo { + gchar *host; + gchar *port; + struct addrinfo *addr; + struct addrinfo *current; + struct addrinfo *tmp; + + pthread_mutex_t *mutex_idle; + guint idle_id; + gint ret; + gchar *message; + GmNet *net; +} threadinfo; + struct _GmNetPrivate { int socket; /**< the connection socket, is -1 if not connected */ GIOChannel *channel; /**< the channel which is used to 'listen' on the socket via the glib main loop */ @@ -31,6 +46,10 @@ struct _GmNetPrivate { int tn_last; /**< used for telnet */ int tn_subneg; /**< used for telnet */ + pthread_t thread; + threadinfo *thread_info; + pthread_mutex_t mutex_idle; + struct addrinfo *addr; struct addrinfo *current; @@ -70,10 +89,35 @@ gboolean on_gm_net_connect_check(GIOChannel * source, GIOCondition condition, GmNet *net); gboolean on_gm_net_connect_timeout(GmNet *net); +static void +gm_net_free_thread_info(GmNet *net) { + g_free(net->priv->thread_info->message); + g_free(net->priv->thread_info->host); + g_free(net->priv->thread_info->port); + g_free(net->priv->thread_info); + + net->priv->thread_info = NULL; +} + +static void +gm_net_close_thread(GmNet *net) { + if (net->priv->thread != 0) { + pthread_kill(net->priv->thread, SIGKILL); + + if (net->priv->thread_info->idle_id) { + g_source_remove(net->priv->thread_info->idle_id); + } + + gm_net_free_thread_info(net); + } +} + static void gm_net_finalize(GObject *object) { GmNet *net = GM_NET(object); + gm_net_close_thread(net); + g_free(net->priv->current_host); g_free(net->priv->current_port); @@ -146,6 +190,8 @@ gm_net_init(GmNet *net) { net->priv->source = 0; net->priv->connect_timeout_id = 0; net->priv->connect_check_id = 0; + + pthread_mutex_init(&(net->priv->mutex_idle), NULL); } static void @@ -191,6 +237,11 @@ gm_net_clean_disconnection(GmNet *net) { } g_io_channel_unref(net->priv->channel); + + if (net->priv->source) { + g_source_remove(net->priv->source); + net->priv->source = 0; + } net->priv->channel = NULL; net->priv->socket = -1; @@ -262,61 +313,13 @@ gm_net_connect_failed(GmNet *net, gchar *err, gint code) { net->priv->current = NULL; } - // Don't use set_state here because we weren't really connected before + g_signal_emit(net, net_signals[NET_ERROR], 0, + _("Could not make connection..."), GM_NET_ERROR_CONNECTING); + gm_net_set_state(net, GM_NET_STATE_DISCONNECTED); } } -void -gm_net_connect_next(GmNet *net) { - char host[NI_MAXHOST], port[NI_MAXSERV]; - int ret, result; - struct addrinfo *tmp = net->priv->current; - - if (tmp == NULL) { - return; - } else { - if ((ret = getnameinfo(tmp->ai_addr, tmp->ai_addrlen, host, NI_MAXHOST, - port, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV)) != 0) { - gm_debug_msg(DEBUG_DEFAULT, "GmNet.ConnectNext: getnameinfo error: %s", - gai_strerror(ret)); - gm_net_connect_failed(net, (gchar *)gai_strerror(ret), ret); - return; - } - - gm_net_set_host(net, host); - gm_net_set_port(net, port); - gm_net_set_state(net, GM_NET_STATE_TRY_ADDRESS); - - net->priv->socket = socket(tmp->ai_family, tmp->ai_socktype, - tmp->ai_protocol); - - if (net->priv->socket < 0) { - gm_net_connect_failed(net, strerror(errno), errno); - } else { - fcntl(net->priv->socket, F_SETFL, - fcntl(net->priv->socket, F_GETFL) | O_NONBLOCK); - - if ((result = connect(net->priv->socket, tmp->ai_addr, - net->priv->addr->ai_addrlen)) == -1 && errno != EINPROGRESS) { - gm_net_connect_failed(net, strerror(errno), errno); - } else { - net->priv->channel = g_io_channel_unix_new(net->priv->socket); - g_io_channel_set_close_on_unref(net->priv->channel, TRUE); - - if (result == 0) { - gm_net_connect_succeed(net); - } else { - net->priv->connect_check_id = g_io_add_watch(net->priv->channel, - G_IO_OUT|G_IO_ERR, (GIOFunc)on_gm_net_connect_check, net); - net->priv->connect_timeout_id = g_timeout_add(5000, - (GSourceFunc)on_gm_net_connect_timeout, net); - } - } - } - } -} - void gm_net_handle_telnet(GmNet *net, unsigned char *buf, int *len) { int i, j; @@ -370,6 +373,163 @@ gm_net_handle_telnet(GmNet *net, unsigned char *buf, int *len) { *len = j; //Since j-- is the last written char } +static gboolean +idle_proceed_connect_next(gpointer user_data) { + threadinfo *info = (threadinfo *)user_data; + GmNet *net = info->net; + struct addrinfo *tmp; + gint ret = info->ret; + gint result; + + info->idle_id = 0; + pthread_join(net->priv->thread, NULL); + net->priv->thread = 0; + + if (info->ret != 0) { + gm_debug_msg(DEBUG_DEFAULT, "GmNet.ConnectNext: getnameinfo error: %s", + gai_strerror(info->ret)); + gm_net_free_thread_info(net); + gm_net_connect_failed(net, (gchar *)gai_strerror(ret), ret); + return FALSE; + } + + gm_net_set_host(net, info->host); + gm_net_set_port(net, info->port); + gm_net_set_state(net, GM_NET_STATE_TRY_ADDRESS); + + tmp = info->tmp; + gm_net_free_thread_info(net); + + net->priv->socket = socket(tmp->ai_family, tmp->ai_socktype, + tmp->ai_protocol); + + if (net->priv->socket < 0) { + gm_net_connect_failed(net, strerror(errno), errno); + } else { + fcntl(net->priv->socket, F_SETFL, + fcntl(net->priv->socket, F_GETFL) | O_NONBLOCK); + + if ((result = connect(net->priv->socket, tmp->ai_addr, + net->priv->addr->ai_addrlen)) == -1 && errno != EINPROGRESS) { + gm_net_connect_failed(net, strerror(errno), errno); + } else { + net->priv->channel = g_io_channel_unix_new(net->priv->socket); + g_io_channel_set_close_on_unref(net->priv->channel, TRUE); + + if (result == 0) { + gm_net_connect_succeed(net); + } else { + net->priv->connect_check_id = g_io_add_watch(net->priv->channel, + G_IO_OUT|G_IO_ERR, (GIOFunc)on_gm_net_connect_check, net); + net->priv->connect_timeout_id = g_timeout_add(5000, + (GSourceFunc)on_gm_net_connect_timeout, net); + } + } + } + + return FALSE; +} + +void +mutex_unlock(void *ptr) { + pthread_mutex_unlock((pthread_mutex_t *)ptr); +} + +void * +nameinfo_thread(void *ptr) { + threadinfo *info = (threadinfo *)ptr; + char host[NI_MAXHOST], port[NI_MAXSERV]; + info->tmp = info->current; + + info->ret = getnameinfo(info->tmp->ai_addr, info->tmp->ai_addrlen, host, + NI_MAXHOST, port, NI_MAXSERV, NI_NUMERICHOST | NI_NUMERICSERV); + + if (info->ret == 0) { + info->host = g_strdup(host); + info->port = g_strdup(port); + } + + pthread_mutex_lock(info->mutex_idle); + pthread_cleanup_push(mutex_unlock, info->mutex_idle); + info->idle_id = g_idle_add(idle_proceed_connect_next, info); + pthread_cleanup_pop(1); + + return NULL; +} + +void +gm_net_connect_next(GmNet *net) { + threadinfo *info; + + if (net->priv->current == NULL) { + return; + } else { + info = g_new0(threadinfo, 1); + info->net = net; + info->current = net->priv->current; + info->mutex_idle = &(net->priv->mutex_idle); + + net->priv->thread_info = info; + pthread_create(&(net->priv->thread), NULL, nameinfo_thread, (void *)info); + } +} + +static gboolean +idle_proceed_addrinfo(gpointer user_data) { + threadinfo *info = (threadinfo *)user_data; + GmNet *net = info->net; + + info->idle_id = 0; + pthread_join(net->priv->thread, NULL); + net->priv->thread = 0; + + if (info->ret != 0) { + gm_debug_msg(DEBUG_DEFAULT, "GmNet.Connect: getaddrinfo failed: %s", + gai_strerror(info->ret)); + gm_net_connect_failed(net, (gchar *)gai_strerror(info->ret), + info->ret); + gm_net_free_thread_info(net); + return FALSE; + } + + net->priv->addr = info->addr; + + if (info->addr != NULL) { + net->priv->current = info->addr; + gm_net_free_thread_info(net); + gm_net_connect_next(net); + } else { + gm_net_free_thread_info(net); + gm_net_connect_failed(net, _("No addresses available"), 0); + } + + return FALSE; +} + +static void * +addrinfo_thread(void *ptr) { + threadinfo *info = (threadinfo *)ptr; + struct addrinfo hint; + + memset(&hint, 0, sizeof(hint)); + hint.ai_flags = 0; + hint.ai_family = AF_UNSPEC; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + + gm_debug_msg(DEBUG_DEFAULT, "GmNet.Connect: getaddrinfo: %s : %s", + info->host, info->port); + + info->ret = getaddrinfo(info->host, info->port, &hint, &(info->addr)); + + pthread_mutex_lock(info->mutex_idle); + pthread_cleanup_push(mutex_unlock, info->mutex_idle); + info->idle_id = g_idle_add(idle_proceed_addrinfo, info); + pthread_cleanup_pop(1); + + return NULL; +} + /* Public */ GmNet * gm_net_new() { @@ -385,10 +545,9 @@ gm_net_state(GmNet *net) { void gm_net_connect(GmNet *net, const gchar *host, const gchar *port) { - struct addrinfo hint; - int ret; char shost[NI_MAXHOST], sport[NI_MAXSERV]; - + threadinfo *info; + if (net->priv->state != GM_NET_STATE_DISCONNECTED) { return; } @@ -401,36 +560,33 @@ gm_net_connect(GmNet *net, const gchar *host, const gchar *port) { gm_net_set_state(net, GM_NET_STATE_CONNECTING); - memset(&hint, 0, sizeof(hint)); - hint.ai_flags = 0; - hint.ai_family = AF_UNSPEC; - hint.ai_socktype = SOCK_STREAM; - hint.ai_protocol = IPPROTO_TCP; - - gm_debug_msg(DEBUG_DEFAULT, "GmNet.Connect: getaddrinfo: %s : %s", shost, sport); + info = g_new0(threadinfo, 1); + info->host = g_strdup(shost); + info->port = g_strdup(sport); + info->net = net; + info->mutex_idle = &(net->priv->mutex_idle); - if ((ret = getaddrinfo(shost, sport, &hint, &(net->priv->addr))) != 0) { - net->priv->addr = NULL; - gm_debug_msg(DEBUG_DEFAULT, "GmNet.Connect: getaddrinfo failed: %s", gai_strerror(ret)); - gm_net_connect_failed(net, (gchar *)gai_strerror(ret), ret); - return; - } - - if (net->priv->addr != NULL) { - net->priv->current = net->priv->addr; - gm_net_connect_next(net); - } else { - gm_net_connect_failed(net, _("No addresses available"), 0); - } + net->priv->thread_info = info; + pthread_create(&(net->priv->thread), NULL, addrinfo_thread, (void *)info); } void gm_net_disconnect(GmNet *net) { - if (net->priv->state == GM_NET_STATE_CONNECTED) { + // thread running + if (net->priv->state != GM_NET_STATE_DISCONNECTED) { + gm_net_close_thread(net); gm_net_set_state(net, GM_NET_STATE_DISCONNECTING); - // Remove the watch - g_source_remove(net->priv->source); + if (net->priv->connect_timeout_id != 0) { + g_source_remove(net->priv->connect_timeout_id); + net->priv->connect_timeout_id = 0; + } + + if (net->priv->connect_check_id != 0) { + g_source_remove(net->priv->connect_check_id); + net->priv->connect_check_id = 0; + } + gm_net_clean_disconnection(net); } } @@ -553,13 +709,13 @@ on_gm_net_connect_check(GIOChannel * source, GIOCondition condition, gboolean on_gm_net_connect_timeout(GmNet *net) { - net->priv->connect_timeout_id = 0; + net->priv->connect_timeout_id = 0; - if (net->priv->connect_check_id != 0) { - g_source_remove(net->priv->connect_check_id); - net->priv->connect_check_id = 0; - } + if (net->priv->connect_check_id != 0) { + g_source_remove(net->priv->connect_check_id); + net->priv->connect_check_id = 0; + } - gm_net_connect_failed(net, _("Connect timeout (5)"), 0); - return FALSE; + gm_net_connect_failed(net, _("Connect timeout (5)"), 0); + return FALSE; }