/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* camel-service.c : Abstract class for an email service */ /* * * Author : * Bertrand Guiheneuf * * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef ENABLE_THREADS #include #include "e-util/e-msgport.h" #endif #include "e-util/e-host-utils.h" #include "camel-service.h" #include "camel-session.h" #include "camel-exception.h" #include "camel-operation.h" #include "camel-private.h" #define d(x) #define w(x) static CamelObjectClass *parent_class = NULL; /* Returns the class for a CamelService */ #define CSERV_CLASS(so) CAMEL_SERVICE_CLASS (CAMEL_OBJECT_GET_CLASS(so)) static void construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex); static gboolean service_connect(CamelService *service, CamelException *ex); static gboolean service_disconnect(CamelService *service, gboolean clean, CamelException *ex); static void cancel_connect (CamelService *service); static GList *query_auth_types (CamelService *service, CamelException *ex); static char *get_name (CamelService *service, gboolean brief); static char *get_path (CamelService *service); static int service_setv (CamelObject *object, CamelException *ex, CamelArgV *args); static int service_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args); static void camel_service_class_init (CamelServiceClass *camel_service_class) { CamelObjectClass *object_class = CAMEL_OBJECT_CLASS (camel_service_class); parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE); /* virtual method overloading */ object_class->setv = service_setv; object_class->getv = service_getv; /* virtual method definition */ camel_service_class->construct = construct; camel_service_class->connect = service_connect; camel_service_class->disconnect = service_disconnect; camel_service_class->cancel_connect = cancel_connect; camel_service_class->query_auth_types = query_auth_types; camel_service_class->get_name = get_name; camel_service_class->get_path = get_path; } static void camel_service_init (void *o, void *k) { CamelService *service = o; service->priv = g_malloc0(sizeof(*service->priv)); #ifdef ENABLE_THREADS service->priv->connect_lock = e_mutex_new(E_MUTEX_REC); service->priv->connect_op_lock = e_mutex_new(E_MUTEX_SIMPLE); #endif } static void camel_service_finalize (CamelObject *object) { CamelService *service = CAMEL_SERVICE (object); if (service->status == CAMEL_SERVICE_CONNECTED) { CamelException ex; camel_exception_init (&ex); CSERV_CLASS (service)->disconnect (service, TRUE, &ex); if (camel_exception_is_set (&ex)) { w(g_warning ("camel_service_finalize: silent disconnect failure: %s", camel_exception_get_description (&ex))); } camel_exception_clear (&ex); } if (service->url) camel_url_free (service->url); if (service->session) camel_object_unref (CAMEL_OBJECT (service->session)); #ifdef ENABLE_THREADS e_mutex_destroy (service->priv->connect_lock); e_mutex_destroy (service->priv->connect_op_lock); #endif g_free (service->priv); } CamelType camel_service_get_type (void) { static CamelType type = CAMEL_INVALID_TYPE; if (type == CAMEL_INVALID_TYPE) { type = camel_type_register (CAMEL_OBJECT_TYPE, "CamelService", sizeof (CamelService), sizeof (CamelServiceClass), (CamelObjectClassInitFunc) camel_service_class_init, NULL, (CamelObjectInitFunc) camel_service_init, camel_service_finalize ); } return type; } static int service_setv (CamelObject *object, CamelException *ex, CamelArgV *args) { CamelService *service = (CamelService *) object; CamelURL *url = service->url; gboolean reconnect = FALSE; guint32 tag; int i; for (i = 0; i < args->argc; i++) { tag = args->argv[i].tag; /* make sure this arg wasn't already handled */ if (tag & CAMEL_ARG_IGNORE) continue; /* make sure this is an arg we're supposed to handle */ if ((tag & CAMEL_ARG_TAG) <= CAMEL_SERVICE_ARG_FIRST || (tag & CAMEL_ARG_TAG) >= CAMEL_SERVICE_ARG_FIRST + 100) continue; if (tag == CAMEL_SERVICE_USERNAME) { /* set the username */ if (strcmp (url->user, args->argv[i].ca_str) != 0) { camel_url_set_user (url, args->argv[i].ca_str); reconnect = TRUE; } } else if (tag == CAMEL_SERVICE_AUTH) { /* set the auth mechanism */ if (strcmp (url->authmech, args->argv[i].ca_str) != 0) { camel_url_set_authmech (url, args->argv[i].ca_str); reconnect = TRUE; } } else if (tag == CAMEL_SERVICE_HOSTNAME) { /* set the hostname */ if (strcmp (url->host, args->argv[i].ca_str) != 0) { camel_url_set_host (url, args->argv[i].ca_str); reconnect = TRUE; } } else if (tag == CAMEL_SERVICE_PORT) { /* set the port */ if (url->port != args->argv[i].ca_int) { camel_url_set_port (url, args->argv[i].ca_int); reconnect = TRUE; } } else if (tag == CAMEL_SERVICE_PATH) { /* set the path */ if (strcmp (url->path, args->argv[i].ca_str) != 0) { camel_url_set_host (url, args->argv[i].ca_str); reconnect = TRUE; } } else { /* error? */ continue; } /* let our parent know that we've handled this arg */ camel_argv_ignore (args, i); } /* FIXME: what if we are in the process of connecting? */ if (reconnect && service->status == CAMEL_SERVICE_CONNECTED) { /* reconnect the service using the new URL */ if (camel_service_disconnect (service, TRUE, ex)) camel_service_connect (service, ex); } return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args); } static int service_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args) { CamelService *service = (CamelService *) object; CamelURL *url = service->url; guint32 tag; int i; for (i = 0; i < args->argc; i++) { tag = args->argv[i].tag; /* make sure this is an arg we're supposed to handle */ if ((tag & CAMEL_ARG_TAG) <= CAMEL_SERVICE_ARG_FIRST || (tag & CAMEL_ARG_TAG) >= CAMEL_SERVICE_ARG_FIRST + 100) continue; switch (tag) { case CAMEL_SERVICE_USERNAME: /* get the username */ *args->argv[i].ca_str = url->user; break; case CAMEL_SERVICE_AUTH: /* get the auth mechanism */ *args->argv[i].ca_str = url->authmech; break; case CAMEL_SERVICE_HOSTNAME: /* get the hostname */ *args->argv[i].ca_str = url->host; break; case CAMEL_SERVICE_PORT: /* get the port */ *args->argv[i].ca_int = url->port; break; case CAMEL_SERVICE_PATH: /* get the path */ *args->argv[i].ca_str = url->path; break; default: /* error? */ break; } } return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args); } static void construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex) { char *url_string; if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_USER) && (url->user == NULL || url->user[0] == '\0')) { url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD); camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, _("URL '%s' needs a username component"), url_string); g_free (url_string); return; } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_HOST) && (url->host == NULL || url->host[0] == '\0')) { url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD); camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, _("URL '%s' needs a host component"), url_string); g_free (url_string); return; } else if (CAMEL_PROVIDER_NEEDS (provider, CAMEL_URL_PART_PATH) && (url->path == NULL || url->path[0] == '\0')) { url_string = camel_url_to_string (url, CAMEL_URL_HIDE_PASSWORD); camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_URL_INVALID, _("URL '%s' needs a path component"), url_string); g_free (url_string); return; } service->provider = provider; service->url = url; service->session = session; camel_object_ref (CAMEL_OBJECT (session)); service->status = CAMEL_SERVICE_DISCONNECTED; } /** * camel_service_construct: * @service: the CamelService * @session: the session for the service * @provider: the service's provider * @url: the default URL for the service (may be NULL) * @ex: a CamelException * * Constructs a CamelService initialized with the given parameters. **/ void camel_service_construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex) { g_return_if_fail (CAMEL_IS_SERVICE (service)); g_return_if_fail (CAMEL_IS_SESSION (session)); CSERV_CLASS (service)->construct (service, session, provider, url, ex); } static gboolean service_connect (CamelService *service, CamelException *ex) { /* Things like the CamelMboxStore can validly * not define a connect function. */ return TRUE; } /** * camel_service_connect: * @service: CamelService object * @ex: a CamelException * * Connect to the service using the parameters it was initialized * with. * * Return value: whether or not the connection succeeded **/ gboolean camel_service_connect (CamelService *service, CamelException *ex) { gboolean ret = FALSE; gboolean unreg = FALSE; g_return_val_if_fail (CAMEL_IS_SERVICE (service), FALSE); g_return_val_if_fail (service->session != NULL, FALSE); g_return_val_if_fail (service->url != NULL, FALSE); CAMEL_SERVICE_LOCK (service, connect_lock); if (service->status == CAMEL_SERVICE_CONNECTED) { CAMEL_SERVICE_UNLOCK (service, connect_lock); return TRUE; } /* Register a separate operation for connecting, so that * the offline code can cancel it. */ CAMEL_SERVICE_LOCK (service, connect_op_lock); service->connect_op = camel_operation_registered (); if (!service->connect_op) { service->connect_op = camel_operation_new (NULL, NULL); camel_operation_register (service->connect_op); unreg = TRUE; } CAMEL_SERVICE_UNLOCK (service, connect_op_lock); service->status = CAMEL_SERVICE_CONNECTING; ret = CSERV_CLASS (service)->connect (service, ex); service->status = ret ? CAMEL_SERVICE_CONNECTED : CAMEL_SERVICE_DISCONNECTED; CAMEL_SERVICE_LOCK (service, connect_op_lock); if (service->connect_op) { if (unreg) camel_operation_unregister (service->connect_op); camel_operation_unref (service->connect_op); service->connect_op = NULL; } CAMEL_SERVICE_UNLOCK (service, connect_op_lock); CAMEL_SERVICE_UNLOCK (service, connect_lock); return ret; } static gboolean service_disconnect (CamelService *service, gboolean clean, CamelException *ex) { /*service->connect_level--;*/ /* We let people get away with not having a disconnect * function -- CamelMboxStore, for example. */ return TRUE; } /** * camel_service_disconnect: * @service: CamelService object * @clean: whether or not to try to disconnect cleanly. * @ex: a CamelException * * Disconnect from the service. If @clean is %FALSE, it should not * try to do any synchronizing or other cleanup of the connection. * * Return value: whether or not the disconnection succeeded without * errors. (Consult @ex if %FALSE.) **/ gboolean camel_service_disconnect (CamelService *service, gboolean clean, CamelException *ex) { gboolean res = TRUE; int unreg = FALSE; CAMEL_SERVICE_LOCK (service, connect_lock); if (service->status != CAMEL_SERVICE_DISCONNECTED && service->status != CAMEL_SERVICE_DISCONNECTING) { CAMEL_SERVICE_LOCK (service, connect_op_lock); service->connect_op = camel_operation_registered (); if (!service->connect_op) { service->connect_op = camel_operation_new (NULL, NULL); camel_operation_register (service->connect_op); unreg = TRUE; } CAMEL_SERVICE_UNLOCK (service, connect_op_lock); service->status = CAMEL_SERVICE_DISCONNECTING; res = CSERV_CLASS (service)->disconnect (service, clean, ex); service->status = CAMEL_SERVICE_DISCONNECTED; CAMEL_SERVICE_LOCK (service, connect_op_lock); if (unreg) camel_operation_unregister (service->connect_op); camel_operation_unref (service->connect_op); service->connect_op = NULL; CAMEL_SERVICE_UNLOCK (service, connect_op_lock); } CAMEL_SERVICE_UNLOCK (service, connect_lock); return res; } static void cancel_connect (CamelService *service) { camel_operation_cancel (service->connect_op); } /** * camel_service_cancel_connect: * @service: a service * * If @service is currently attempting to connect to or disconnect * from a server, this causes it to stop and fail. Otherwise it is a * no-op. **/ void camel_service_cancel_connect (CamelService *service) { CAMEL_SERVICE_LOCK (service, connect_op_lock); if (service->connect_op) CSERV_CLASS (service)->cancel_connect (service); CAMEL_SERVICE_UNLOCK (service, connect_op_lock); } /** * camel_service_get_url: * @service: a service * * Returns the URL representing a service. The returned URL must be * freed when it is no longer needed. For security reasons, this * routine does not return the password. * * Return value: the url name **/ char * camel_service_get_url (CamelService *service) { return camel_url_to_string (service->url, CAMEL_URL_HIDE_PASSWORD); } static char * get_name (CamelService *service, gboolean brief) { w(g_warning ("CamelService::get_name not implemented for `%s'", camel_type_to_name (CAMEL_OBJECT_GET_TYPE (service)))); return g_strdup ("???"); } /** * camel_service_get_name: * @service: the service * @brief: whether or not to use a briefer form * * This gets the name of the service in a "friendly" (suitable for * humans) form. If @brief is %TRUE, this should be a brief description * such as for use in the folder tree. If @brief is %FALSE, it should * be a more complete and mostly unambiguous description. * * Return value: the description, which the caller must free. **/ char * camel_service_get_name (CamelService *service, gboolean brief) { g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL); g_return_val_if_fail (service->url, NULL); return CSERV_CLASS (service)->get_name (service, brief); } static char * get_path (CamelService *service) { CamelProvider *prov = service->provider; CamelURL *url = service->url; GString *gpath; char *path; /* A sort of ad-hoc default implementation that works for our * current set of services. */ gpath = g_string_new (service->provider->protocol); if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_USER)) { if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) { g_string_append_printf (gpath, "/%s@%s", url->user ? url->user : "", url->host ? url->host : ""); if (url->port) g_string_append_printf (gpath, ":%d", url->port); } else { g_string_append_printf (gpath, "/%s%s", url->user ? url->user : "", CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_USER) ? "" : "@"); } } else if (CAMEL_PROVIDER_ALLOWS (prov, CAMEL_URL_PART_HOST)) { g_string_append_printf (gpath, "/%s%s", CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_HOST) ? "" : "@", url->host ? url->host : ""); if (url->port) g_string_append_printf (gpath, ":%d", url->port); } if (CAMEL_PROVIDER_NEEDS (prov, CAMEL_URL_PART_PATH)) g_string_append_printf (gpath, "%s%s", *url->path == '/' ? "" : "/", url->path); path = gpath->str; g_string_free (gpath, FALSE); return path; } /** * camel_service_get_path: * @service: the service * * This gets a valid UNIX relative path describing the service, which * is guaranteed to be different from the path returned for any * different service. This path MUST start with the name of the * provider, followed by a "/", but after that, it is up to the * provider. * * Return value: the path, which the caller must free. **/ char * camel_service_get_path (CamelService *service) { g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL); g_return_val_if_fail (service->url, NULL); return CSERV_CLASS (service)->get_path (service); } /** * camel_service_get_session: * @service: a service * * Returns the CamelSession associated with the service. * * Return value: the session **/ CamelSession * camel_service_get_session (CamelService *service) { return service->session; } /** * camel_service_get_provider: * @service: a service * * Returns the CamelProvider associated with the service. * * Return value: the provider **/ CamelProvider * camel_service_get_provider (CamelService *service) { return service->provider; } static GList * query_auth_types (CamelService *service, CamelException *ex) { return NULL; } /** * camel_service_query_auth_types: * @service: a CamelService * @ex: a CamelException * * This is used by the mail source wizard to get the list of * authentication types supported by the protocol, and information * about them. * * Return value: a list of CamelServiceAuthType records. The caller * must free the list with g_list_free() when it is done with it. **/ GList * camel_service_query_auth_types (CamelService *service, CamelException *ex) { GList *ret; /* note that we get the connect lock here, which means the callee must not call the connect functions itself */ CAMEL_SERVICE_LOCK (service, connect_lock); ret = CSERV_CLASS (service)->query_auth_types (service, ex); CAMEL_SERVICE_UNLOCK (service, connect_lock); return ret; } /* URL utility routines */ /** * camel_service_gethost: * @service: a CamelService * @ex: a CamelException * * This is a convenience function to do a gethostbyname on the host * for the service's URL. * * Return value: a (statically-allocated) hostent. **/ struct hostent * camel_service_gethost (CamelService *service, CamelException *ex) { char *hostname; if (service->url->host) hostname = service->url->host; else hostname = "localhost"; return camel_gethostbyname (hostname, ex); } #ifdef offsetof #define STRUCT_OFFSET(type, field) ((gint) offsetof (type, field)) #else #define STRUCT_OFFSET(type, field) ((gint) ((gchar*) &((type *) 0)->field)) #endif struct _lookup_msg { #ifdef ENABLE_THREADS EMsg msg; #endif unsigned int cancelled:1; const char *name; int len; int type; int result; int herr; struct hostent hostbuf; int hostbuflen; char *hostbufmem; }; static void * get_hostbyname(void *data) { struct _lookup_msg *info = data; while ((info->result = e_gethostbyname_r(info->name, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->herr)) == ERANGE) { d(printf("gethostbyname fialed?\n")); #ifdef ENABLE_THREADS pthread_testcancel(); #endif info->hostbuflen *= 2; info->hostbufmem = g_realloc(info->hostbufmem, info->hostbuflen); } d(printf("gethostbyname ok?\n")); #ifdef ENABLE_THREADS /* If we got cancelled, dont reply, just free it */ if (info->cancelled) { g_free(info->hostbufmem); g_free(info); } else { e_msgport_reply((EMsg *)info); } #endif return NULL; } struct hostent * camel_gethostbyname (const char *name, CamelException *ex) { #ifdef ENABLE_THREADS int fdmax, status, fd, cancel_fd; #endif struct _lookup_msg *msg; g_return_val_if_fail(name != NULL, NULL); if (camel_operation_cancel_check(NULL)) { camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); return NULL; } camel_operation_start_transient(NULL, _("Resolving: %s"), name); msg = g_malloc0(sizeof(*msg)); msg->hostbuflen = 1024; msg->hostbufmem = g_malloc(msg->hostbuflen); msg->name = name; msg->result = -1; #ifdef ENABLE_THREADS cancel_fd = camel_operation_cancel_fd(NULL); if (cancel_fd == -1) { #endif get_hostbyname(msg); #ifdef ENABLE_THREADS } else { EMsgPort *reply_port; pthread_t id; fd_set rdset; reply_port = msg->msg.reply_port = e_msgport_new(); fd = e_msgport_fd(msg->msg.reply_port); if (pthread_create(&id, NULL, get_hostbyname, msg) == 0) { d(printf("waiting for name return/cancellation in main process\n")); do { FD_ZERO(&rdset); FD_SET(cancel_fd, &rdset); FD_SET(fd, &rdset); fdmax = MAX(fd, cancel_fd) + 1; status = select(fdmax, &rdset, NULL, 0, NULL); } while (status == -1 && errno == EINTR); if (status == -1 || FD_ISSET(cancel_fd, &rdset)) { if (status == -1) camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Failure in name lookup: %s"), g_strerror(errno)); else camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); /* We cancel so if the thread impl is decent it causes immediate exit. We detach so we dont need to wait for it to exit if it isn't. We check the reply port incase we had a reply in the mean time, which we free later */ d(printf("Cancelling lookup thread and leaving it\n")); msg->cancelled = 1; pthread_detach(id); pthread_cancel(id); msg = (struct _lookup_msg *)e_msgport_get(reply_port); } else { struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port); g_assert(reply == msg); d(printf("waiting for child to exit\n")); pthread_join(id, NULL); d(printf("child done\n")); } } else { camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: cannot create thread: %s"), g_strerror(errno)); } e_msgport_destroy(reply_port); } #endif camel_operation_end(NULL); if (!camel_exception_is_set(ex)) { if (msg->result == 0) return &msg->hostbuf; if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA) camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: %s: host not found"), name); else camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: %s: unknown reason"), name); } if (msg) { g_free(msg->hostbufmem); g_free(msg); } return NULL; } static void * get_hostbyaddr (void *data) { struct _lookup_msg *info = data; while ((info->result = e_gethostbyaddr_r (info->name, info->len, info->type, &info->hostbuf, info->hostbufmem, info->hostbuflen, &info->herr)) == ERANGE) { d(printf ("gethostbyaddr fialed?\n")); #ifdef ENABLE_THREADS pthread_testcancel (); #endif info->hostbuflen *= 2; info->hostbufmem = g_realloc (info->hostbufmem, info->hostbuflen); } d(printf ("gethostbyaddr ok?\n")); #ifdef ENABLE_THREADS if (info->cancelled) { g_free(info->hostbufmem); g_free(info); } else { e_msgport_reply((EMsg *)info); } #endif return NULL; } struct hostent * camel_gethostbyaddr (const char *addr, int len, int type, CamelException *ex) { #ifdef ENABLE_THREADS int fdmax, status, fd, cancel_fd; #endif struct _lookup_msg *msg; g_return_val_if_fail (addr != NULL, NULL); if (camel_operation_cancel_check (NULL)) { camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); return NULL; } camel_operation_start_transient (NULL, _("Resolving address")); msg = g_malloc0 (sizeof (struct _lookup_msg)); msg->hostbuflen = 1024; msg->hostbufmem = g_malloc (msg->hostbuflen); msg->name = addr; msg->len = len; msg->type = type; msg->result = -1; #ifdef ENABLE_THREADS cancel_fd = camel_operation_cancel_fd (NULL); if (cancel_fd == -1) { #endif get_hostbyaddr (msg); #ifdef ENABLE_THREADS } else { EMsgPort *reply_port; pthread_t id; fd_set rdset; reply_port = msg->msg.reply_port = e_msgport_new (); fd = e_msgport_fd (msg->msg.reply_port); if (pthread_create (&id, NULL, get_hostbyaddr, msg) == 0) { d(printf("waiting for name return/cancellation in main process\n")); do { FD_ZERO(&rdset); FD_SET(cancel_fd, &rdset); FD_SET(fd, &rdset); fdmax = MAX(fd, cancel_fd) + 1; status = select (fdmax, &rdset, NULL, 0, NULL); } while (status == -1 && errno == EINTR); if (status == -1 || FD_ISSET(cancel_fd, &rdset)) { if (status == -1) camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Failure in name lookup: %s"), g_strerror(errno)); else camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled")); /* We cancel so if the thread impl is decent it causes immediate exit. We detach so we dont need to wait for it to exit if it isn't. We check the reply port incase we had a reply in the mean time, which we free later */ d(printf("Cancelling lookup thread and leaving it\n")); msg->cancelled = 1; pthread_detach(id); pthread_cancel(id); msg = (struct _lookup_msg *)e_msgport_get(reply_port); } else { struct _lookup_msg *reply = (struct _lookup_msg *)e_msgport_get(reply_port); g_assert(reply == msg); d(printf("waiting for child to exit\n")); pthread_join(id, NULL); d(printf("child done\n")); } } e_msgport_destroy (reply_port); } #endif camel_operation_end (NULL); if (!camel_exception_is_set(ex)) { if (msg->result == 0) return &msg->hostbuf; if (msg->herr == HOST_NOT_FOUND || msg->herr == NO_DATA) camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: host not found")); else camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Host lookup failed: unknown reason")); } if (msg) { g_free(msg->hostbufmem); g_free(msg); } return NULL; } void camel_free_host(struct hostent *h) { struct _lookup_msg *msg; g_return_if_fail(h != NULL); /* yeah this looks ugly but it is safe. we passed out a reference to inside our structure, this maps it to the base structure, so we can free everything right without having to keep track of it separately */ msg = (struct _lookup_msg *)(((char *)h) - STRUCT_OFFSET(struct _lookup_msg, hostbuf)); g_free(msg->hostbufmem); g_free(msg); }