Thu, 07 Jun 2012 10:47:50 +0000
New custom resolver, that uses libpurple for DNS queries. Get rid of old win32 resolver. Refs#343. Fixes #6263
--- a/libpurple/eventloop.c Thu May 31 17:16:24 2012 +0000 +++ b/libpurple/eventloop.c Thu Jun 07 10:47:50 2012 +0000 @@ -91,6 +91,16 @@ } } +int +purple_input_pipe(int pipefd[2]) +{ +#ifdef _WIN32 + return wpurple_input_pipe(pipefd); +#else + return pipe(pipefd); +#endif +} + void purple_eventloop_set_ui_ops(PurpleEventLoopUiOps *ops) {
--- a/libpurple/eventloop.h Thu May 31 17:16:24 2012 +0000 +++ b/libpurple/eventloop.h Thu Jun 07 10:47:50 2012 +0000 @@ -240,6 +240,24 @@ int purple_input_get_error(int fd, int *error); +/** + * Creates a pipe - an unidirectional data channel that can be used for + * interprocess communication. + * + * File descriptors for both ends of pipe will be written into provided array. + * The first one (pipefd[0]) can be used for reading, the second one (pipefd[1]) + * for writing. + * + * On Windows it's simulated by creating a pair of connected sockets, on other + * systems pipe() is used. + * + * @param pipefd Array used to return file descriptors for both ends of pipe. + * + * @return @c 0 on success, @c -1 on error. + */ +int +purple_input_pipe(int pipefd[2]); + /*@}*/
--- a/libpurple/protocols/gg/Makefile.am Thu May 31 17:16:24 2012 +0000 +++ b/libpurple/protocols/gg/Makefile.am Thu Jun 07 10:47:50 2012 +0000 @@ -1,7 +1,7 @@ EXTRA_DIST = \ Makefile.mingw \ - win32-resolver.c \ - win32-resolver.h \ + resolver-purple.c \ + resolver-purple.h \ lib/common.c \ lib/compat.h \ lib/COPYING \ @@ -81,7 +81,9 @@ buddylist.h \ buddylist.c \ gg.h \ - gg.c + gg.c \ + resolver-purple.h \ + resolver-purple.c AM_CFLAGS = $(st)
--- a/libpurple/protocols/gg/Makefile.mingw Thu May 31 17:16:24 2012 +0000 +++ b/libpurple/protocols/gg/Makefile.mingw Thu Jun 07 10:47:50 2012 +0000 @@ -24,14 +24,14 @@ ## ## INCLUDE PATHS ## -INCLUDE_PATHS += -I. \ +INCLUDE_PATHS +=\ + -I$(PIDGIN_TREE_TOP) \ + -I$(PURPLE_TOP) \ + -I$(PURPLE_TOP)/win32 \ -I./lib \ -I$(GTK_TOP)/include \ -I$(GTK_TOP)/include/glib-2.0 \ - -I$(GTK_TOP)/lib/glib-2.0/include \ - -I$(PURPLE_TOP) \ - -I$(PURPLE_TOP)/win32 \ - -I$(PIDGIN_TREE_TOP) + -I$(GTK_TOP)/lib/glib-2.0/include LIB_PATHS += -L$(GTK_TOP)/lib \ -L$(PURPLE_TOP) \ @@ -61,7 +61,7 @@ gg.c \ search.c \ gg-utils.c \ - win32-resolver.c + resolver-purple.c OBJECTS = $(C_SRC:%.c=%.o)
--- a/libpurple/protocols/gg/gg.c Thu May 31 17:16:24 2012 +0000 +++ b/libpurple/protocols/gg/gg.c Thu Jun 07 10:47:50 2012 +0000 @@ -44,10 +44,7 @@ #include "search.h" #include "buddylist.h" #include "gg-utils.h" - -#ifdef _WIN32 -# include "win32-resolver.h" -#endif +#include "resolver-purple.h" /* Prototypes */ static void ggp_set_status(PurpleAccount *account, PurpleStatus *status); @@ -3005,10 +3002,7 @@ gg_debug_handler = purple_gg_debug_handler; -#ifdef _WIN32 - gg_global_set_custom_resolver(ggp_resolver_win32thread_start, - ggp_resolver_win32thread_cleanup); -#endif + ggp_resolver_purple_setup(); } PURPLE_INIT_PLUGIN(gg, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/resolver-purple.c Thu Jun 07 10:47:50 2012 +0000 @@ -0,0 +1,161 @@ +#include <internal.h> +#include <debug.h> +#include <dnsquery.h> + +#include <libgadu.h> +#include "resolver-purple.h" + +static int ggp_resolver_purple_start(int *fd, void **private_data, + const char *hostname); + +static void ggp_resolver_purple_cleanup(void **private_data, int force); + +static void ggp_resolver_purple_cb(GSList *hosts, gpointer cbdata, + const char *error_message); + +typedef struct +{ + PurpleDnsQueryData *purpleQuery; + + /** + * File descriptors: + * pipes[0] - for reading + * pipes[1] - for writing + */ + int pipes[2]; +} ggp_resolver_purple_data; + + +extern void ggp_resolver_purple_setup(void) +{ + purple_debug_info("gg", "setting up custom resolver\n"); + gg_global_set_custom_resolver(ggp_resolver_purple_start, + ggp_resolver_purple_cleanup); +} + +void ggp_resolver_purple_cb(GSList *hosts, gpointer cbdata, + const char *error_message) +{ + ggp_resolver_purple_data *data = (ggp_resolver_purple_data*)cbdata; + const int fd = data->pipes[1]; + int ipv4_count, all_count, write_size; + struct in_addr *addresses; + + purple_debug_misc("gg", "ggp_resolver_purple_cb(%x, %x, \"%s\")\n", + (unsigned int)hosts, (unsigned int)cbdata, error_message); + + if (error_message) + { + purple_debug_error("gg", "ggp_resolver_purple_cb failed: %s\n", + error_message); + } + + all_count = g_slist_length(hosts); + g_assert(all_count % 2 == 0); + all_count /= 2; + addresses = malloc((all_count + 1) * sizeof(struct in_addr)); + + ipv4_count = 0; + while (hosts && (hosts = g_slist_delete_link(hosts, hosts))) + { + const struct sockaddr *addr = hosts->data; + char dst[INET6_ADDRSTRLEN]; + + if (addr->sa_family == AF_INET6) + { + inet_ntop(addr->sa_family, + &((struct sockaddr_in6 *) addr)->sin6_addr, + dst, sizeof(dst)); + purple_debug_misc("gg", "ggp_resolver_purple_cb " + "ipv6 (ignore): %s\n", dst); + } + else if (addr->sa_family == AF_INET) + { + const struct in_addr addr_ipv4 = + ((struct sockaddr_in *) addr)->sin_addr; + inet_ntop(addr->sa_family, &addr_ipv4, + dst, sizeof(dst)); + purple_debug_misc("gg", "ggp_resolver_purple_cb " + "ipv4: %s\n", dst); + + g_assert(ipv4_count < all_count); + addresses[ipv4_count++] = addr_ipv4; + } + else + { + purple_debug_warning("gg", "ggp_resolver_purple_cb " + "unexpected sa_family: %d\n", addr->sa_family); + } + + g_free(hosts->data); + hosts = g_slist_delete_link(hosts, hosts); + } + + addresses[ipv4_count].s_addr = INADDR_NONE; + + write_size = (ipv4_count + 1) * sizeof(struct in_addr); + if (write(fd, addresses, write_size) != write_size) + { + purple_debug_error("gg", + "ggp_resolver_purple_cb write error\n"); + } + free(addresses); +} + +int ggp_resolver_purple_start(int *fd, void **private_data, + const char *hostname) +{ + ggp_resolver_purple_data *data; + purple_debug_misc("gg", "ggp_resolver_purple_start(%x, %x, \"%s\")\n", + (unsigned int)fd, (unsigned int)private_data, hostname); + + data = malloc(sizeof(ggp_resolver_purple_data)); + *private_data = (void*)data; + data->purpleQuery = NULL; + data->pipes[0] = 0; + data->pipes[1] = 0; + + if (purple_input_pipe(data->pipes) != 0) + { + purple_debug_error("gg", "ggp_resolver_purple_start: " + "unable to create pipe\n"); + ggp_resolver_purple_cleanup(private_data, 0); + return -1; + } + + *fd = data->pipes[0]; + + /* account and port is unknown in this context */ + data->purpleQuery = purple_dnsquery_a(NULL, hostname, 80, + ggp_resolver_purple_cb, (gpointer)data); + + if (!data->purpleQuery) + { + purple_debug_error("gg", "ggp_resolver_purple_start: " + "unable to call purple_dnsquery_a\n"); + ggp_resolver_purple_cleanup(private_data, 0); + return -1; + } + + return 0; +} + +void ggp_resolver_purple_cleanup(void **private_data, int force) +{ + ggp_resolver_purple_data *data = + (ggp_resolver_purple_data*)(*private_data); + + purple_debug_misc("gg", "ggp_resolver_purple_cleanup(%x, %d)\n", + (unsigned int)private_data, force); + + if (!data) + return; + *private_data = NULL; + + if (data->pipes[0]) + close(data->pipes[0]); + if (data->pipes[1]) + close(data->pipes[1]); + + free(data); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libpurple/protocols/gg/resolver-purple.h Thu Jun 07 10:47:50 2012 +0000 @@ -0,0 +1,9 @@ +#ifndef _PURPLE_GG_RESOLVER_PURPLE +#define _PURPLE_GG_RESOLVER_PURPLE + +/** + * Registers custom resolver for libgadu, that uses libpurple for DNS queries. + */ +void ggp_resolver_purple_setup(void); + +#endif /* _PURPLE_GG_RESOLVER_PURPLE */
--- a/libpurple/protocols/gg/win32-resolver.c Thu May 31 17:16:24 2012 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,322 +0,0 @@ -/** - * @file win32-resolver.c - * - * purple - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include "win32-resolver.h" - -#include <errno.h> -#include <resolver.h> -#include "debug.h" - -#ifndef _WIN32 -#error "win32thread resolver is not supported on current platform" -#endif - -/** - * Deal with the fact that you can't select() on a win32 file fd. - * This makes it practically impossible to tie into purple's event loop. - * - * -This is thanks to Tor Lillqvist. - */ -static int ggp_resolver_win32thread_socket_pipe(int *fds) -{ - SOCKET temp, socket1 = -1, socket2 = -1; - struct sockaddr_in saddr; - int len; - u_long arg; - fd_set read_set, write_set; - struct timeval tv; - - purple_debug_misc("gg", "ggp_resolver_win32thread_socket_pipe(&%d)\n", - *fds); - - temp = socket(AF_INET, SOCK_STREAM, 0); - - if (temp == INVALID_SOCKET) { - goto out0; - } - - arg = 1; - if (ioctlsocket(temp, FIONBIO, &arg) == SOCKET_ERROR) { - goto out0; - } - - memset(&saddr, 0, sizeof(saddr)); - saddr.sin_family = AF_INET; - saddr.sin_port = 0; - saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - - if (bind(temp, (struct sockaddr *)&saddr, sizeof (saddr))) { - goto out0; - } - - if (listen(temp, 1) == SOCKET_ERROR) { - goto out0; - } - - len = sizeof(saddr); - if (getsockname(temp, (struct sockaddr *)&saddr, &len)) { - goto out0; - } - - socket1 = socket(AF_INET, SOCK_STREAM, 0); - - if (socket1 == INVALID_SOCKET) { - goto out0; - } - - arg = 1; - if (ioctlsocket(socket1, FIONBIO, &arg) == SOCKET_ERROR) { - goto out1; - } - - if (connect(socket1, (struct sockaddr *)&saddr, len) != SOCKET_ERROR || - WSAGetLastError() != WSAEWOULDBLOCK) { - goto out1; - } - - FD_ZERO(&read_set); - FD_SET(temp, &read_set); - - tv.tv_sec = 0; - tv.tv_usec = 0; - - if (select(0, &read_set, NULL, NULL, NULL) == SOCKET_ERROR) { - goto out1; - } - - if (!FD_ISSET(temp, &read_set)) { - goto out1; - } - - socket2 = accept(temp, (struct sockaddr *) &saddr, &len); - if (socket2 == INVALID_SOCKET) { - goto out1; - } - - FD_ZERO(&write_set); - FD_SET(socket1, &write_set); - - tv.tv_sec = 0; - tv.tv_usec = 0; - - if (select(0, NULL, &write_set, NULL, NULL) == SOCKET_ERROR) { - goto out2; - } - - if (!FD_ISSET(socket1, &write_set)) { - goto out2; - } - - arg = 0; - if (ioctlsocket(socket1, FIONBIO, &arg) == SOCKET_ERROR) { - goto out2; - } - - arg = 0; - if (ioctlsocket(socket2, FIONBIO, &arg) == SOCKET_ERROR) { - goto out2; - } - - fds[0] = socket1; - fds[1] = socket2; - - closesocket (temp); - - return 0; - -out2: - closesocket (socket2); -out1: - closesocket (socket1); -out0: - closesocket (temp); - errno = EIO; /* XXX */ - - return -1; -} - -struct ggp_resolver_win32thread_data { - char *hostname; - int fd; -}; - -/** - * Copy-paste from gg_resolver_run(). - */ -static DWORD WINAPI ggp_resolver_win32thread_thread(LPVOID arg) -{ - struct ggp_resolver_win32thread_data *data = arg; - struct in_addr addr_ip[2], *addr_list; - int addr_count; - - purple_debug_info("gg", "ggp_resolver_win32thread_thread() host: %s, " - "fd: %i called\n", data->hostname, data->fd); - - if ((addr_ip[0].s_addr = inet_addr(data->hostname)) == INADDR_NONE) { - if (gg_gethostbyname_real(data->hostname, &addr_list, - &addr_count, 0) == -1) { - addr_list = addr_ip; - /* addr_ip[0] już zawiera INADDR_NONE */ - } - } else { - addr_list = addr_ip; - addr_ip[1].s_addr = INADDR_NONE; - addr_count = 1; - } - - purple_debug_misc("gg", "ggp_resolver_win32thread_thread() " - "count = %d\n", addr_count); - - write(data->fd, addr_list, (addr_count + 1) * sizeof(struct in_addr)); - close(data->fd); - - free(data->hostname); - data->hostname = NULL; - - free(data); - - if (addr_list != addr_ip) - free(addr_list); - - purple_debug_misc("gg", "ggp_resolver_win32thread_thread() done\n"); - - return 0; -} - - -int ggp_resolver_win32thread_start(int *fd, void **private_data, - const char *hostname) -{ - struct ggp_resolver_win32thread_data *data = NULL; - HANDLE h; - DWORD dwTId; - int pipes[2], new_errno; - - purple_debug_info("gg", "ggp_resolver_win32thread_start(%p, %p, " - "\"%s\");\n", fd, private_data, hostname); - - if (!private_data || !fd || !hostname) { - purple_debug_error("gg", "ggp_resolver_win32thread_start() " - "invalid arguments\n"); - errno = EFAULT; - return -1; - } - - purple_debug_misc("gg", "ggp_resolver_win32thread_start() creating " - "pipes...\n"); - - if (ggp_resolver_win32thread_socket_pipe(pipes) == -1) { - purple_debug_error("gg", "ggp_resolver_win32thread_start() " - "unable to create pipes (errno=%d, %s)\n", - errno, strerror(errno)); - return -1; - } - - if (!(data = malloc(sizeof(*data)))) { - purple_debug_error("gg", "ggp_resolver_win32thread_start() out " - "of memory\n"); - new_errno = errno; - goto cleanup; - } - - data->hostname = NULL; - - if (!(data->hostname = strdup(hostname))) { - purple_debug_error("gg", "ggp_resolver_win32thread_start() out " - "of memory\n"); - new_errno = errno; - goto cleanup; - } - - data->fd = pipes[1]; - - purple_debug_misc("gg", "ggp_resolver_win32thread_start() creating " - "thread...\n"); - - h = CreateThread(NULL, 0, ggp_resolver_win32thread_thread, data, 0, - &dwTId); - - if (h == NULL) { - purple_debug_error("gg", "ggp_resolver_win32thread_start() " - "unable to create thread\n"); - new_errno = errno; - goto cleanup; - } - - *private_data = h; - *fd = pipes[0]; - - purple_debug_misc("gg", "ggp_resolver_win32thread_start() done\n"); - - return 0; - -cleanup: - if (data) { - free(data->hostname); - free(data); - } - - close(pipes[0]); - close(pipes[1]); - - errno = new_errno; - - return -1; - -} - -void ggp_resolver_win32thread_cleanup(void **private_data, int force) -{ - struct ggp_resolver_win32thread_data *data; - - purple_debug_info("gg", "ggp_resolver_win32thread_cleanup() force: %i " - "called\n", force); - - if (private_data == NULL || *private_data == NULL) { - purple_debug_error("gg", "ggp_resolver_win32thread_cleanup() " - "private_data: NULL\n"); - return; - } - return; /* XXX */ - - data = (struct ggp_resolver_win32thread_data*) *private_data; - purple_debug_misc("gg", "ggp_resolver_win32thread_cleanup() data: " - "%s called\n", data->hostname); - *private_data = NULL; - - if (force) { - purple_debug_misc("gg", "ggp_resolver_win32thread_cleanup() " - "force called\n"); - //pthread_cancel(data->thread); - //pthread_join(data->thread, NULL); - } - - free(data->hostname); - data->hostname = NULL; - - if (data->fd != -1) { - close(data->fd); - data->fd = -1; - } - purple_debug_info("gg", "ggp_resolver_win32thread_cleanup() done\n"); - free(data); -} - -/* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/protocols/gg/win32-resolver.h Thu May 31 17:16:24 2012 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -/** - * @file win32-resolver.h - * - * purple - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#ifndef _PURPLE_GG_WIN32_RESOLVER -#define _PURPLE_GG_WIN32_RESOLVER - -/** - * Starts hostname resolving in new win32 thread. - * - * @param fd Pointer to variable, where pipe descriptor will be saved. - * @param private_data Pointer to variable, where pointer to private data will - * be saved. - * @param hostname Hostname to resolve. - */ -int ggp_resolver_win32thread_start(int *fd, void **private_data, - const char *hostname); - -/** - * Cleans up resources after hostname resolving. - * - * @param private_data Pointer to variable storing pointer to private data. - * @param force TRUE, if resources should be cleaned up even, if - * resolving process didn't finished. - */ -void ggp_resolver_win32thread_cleanup(void **private_data, int force); - -#endif /* _PURPLE_GG_WIN32_RESOLVER */ - -/* vim: set ts=8 sts=0 sw=8 noet: */
--- a/libpurple/win32/win32dep.c Thu May 31 17:16:24 2012 +0000 +++ b/libpurple/win32/win32dep.c Thu Jun 07 10:47:50 2012 +0000 @@ -328,6 +328,111 @@ return result; } +int wpurple_input_pipe(int pipefd[2]) +{ + SOCKET sock_server, sock_client, sock_server_established; + struct sockaddr_in saddr_in; + struct sockaddr * const saddr_p = (struct sockaddr *)&saddr_in; + int saddr_len = sizeof(struct sockaddr_in); + u_long arg; + fd_set select_set; + char succ = 1; + + sock_server = sock_client = sock_server_established = INVALID_SOCKET; + + purple_debug_misc("wpurple", "wpurple_input_pipe(0x%x[%d,%d])\n", + (unsigned int)pipefd, pipefd[0], pipefd[1]); + + /* create client and passive server sockets */ + sock_server = socket(AF_INET, SOCK_STREAM, 0); + sock_client = socket(AF_INET, SOCK_STREAM, 0); + succ = (sock_server != INVALID_SOCKET || sock_client != INVALID_SOCKET); + + /* set created sockets into nonblocking mode */ + arg = 1; + succ = (succ && + ioctlsocket(sock_server, FIONBIO, &arg) != SOCKET_ERROR); + arg = 1; + succ = (succ && + ioctlsocket(sock_client, FIONBIO, &arg) != SOCKET_ERROR); + + /* listen on server socket */ + memset(&saddr_in, 0, saddr_len); + saddr_in.sin_family = AF_INET; + saddr_in.sin_port = 0; + saddr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + succ = (succ && + bind(sock_server, saddr_p, saddr_len) != SOCKET_ERROR && + listen(sock_server, 1) != SOCKET_ERROR && + getsockname(sock_server, saddr_p, &saddr_len) != SOCKET_ERROR); + + /* request a connection from client to server socket */ + succ = (succ && + connect(sock_client, saddr_p, saddr_len) == SOCKET_ERROR && + WSAGetLastError() == WSAEWOULDBLOCK); + + /* ensure, that server socket is readable */ + if (succ) + { + FD_ZERO(&select_set); + FD_SET(sock_server, &select_set); + } + succ = (succ && + select(0, &select_set, NULL, NULL, NULL) != SOCKET_ERROR && + FD_ISSET(sock_server, &select_set)); + + /* accept (establish) connection from client socket */ + if (succ) + { + sock_server_established = + accept(sock_server, saddr_p, &saddr_len); + succ = (sock_server_established != INVALID_SOCKET); + } + + /* ensure, that client socket is writable */ + if (succ) + { + FD_ZERO(&select_set); + FD_SET(sock_client, &select_set); + } + succ = (succ && + select(0, NULL, &select_set, NULL, NULL) != SOCKET_ERROR && + FD_ISSET(sock_client, &select_set)); + + /* set sockets into blocking mode */ + arg = 0; + succ = (succ && + ioctlsocket(sock_client, FIONBIO, &arg) != SOCKET_ERROR); + arg = 0; + succ = (succ && + ioctlsocket(sock_server_established, FIONBIO, &arg) + != SOCKET_ERROR); + + /* we don't need (passive) server socket anymore */ + if (sock_server != INVALID_SOCKET) + closesocket(sock_server); + + if (succ) + { + purple_debug_misc("wpurple", + "wpurple_input_pipe created pipe [%d,%d]\n", + sock_client, sock_server_established); + pipefd[0] = sock_client; /* for reading */ + pipefd[1] = sock_server_established; /* for writing */ + return 0; + } + else + { + purple_debug_error("wpurple", "wpurple_input_pipe failed\n"); + if (sock_client != INVALID_SOCKET) + closesocket(sock_client); + if (sock_server_established != INVALID_SOCKET) + closesocket(sock_server_established); + errno = EMFILE; + return -1; + } +} + void wpurple_init(void) { WORD wVersionRequested; WSADATA wsaData;
--- a/libpurple/win32/win32dep.h Thu May 31 17:16:24 2012 +0000 +++ b/libpurple/win32/win32dep.h Thu Jun 07 10:47:50 2012 +0000 @@ -60,6 +60,9 @@ char *wpurple_escape_dirsep(const char *filename); /* needs to be g_free'd */ GIOChannel *wpurple_g_io_channel_win32_new_socket(int socket); /* Until we get the post-2.8 glib win32 giochannel implementation working, use the thread-based one */ +/* Simulate unix pipes by creating a pair of connected sockets */ +int wpurple_input_pipe(int pipefd[2]); + /* Determine Purple paths */ gchar *wpurple_get_special_folder(int folder_type); /* needs to be g_free'd */ const char *wpurple_install_dir(void);