/** * \internal Odpowiednik \c gethostbyname zapewniający współbieżność. * * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli * nie, to zwykłej \c gethostbyname. Funkcja służy do zachowania zgodności * ABI i służy do pobierania tylko pierwszego adresu -- pozostałe mogą * zostać zignorowane przez aplikację. * * \param hostname Nazwa serwera * * \return Zaalokowana struktura \c in_addr lub NULL w przypadku błędu. */ struct in_addr *gg_gethostbyname(const char *hostname) { struct in_addr *result; unsigned int count; if (gg_gethostbyname_real(hostname, &result, &count, 0) == -1) return NULL; return result; }
int gg_gethostbyname_real(const char *hostname, struct in_addr *addr, int pthread) { #ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R char *buf = NULL; char *new_buf = NULL; struct hostent he; struct hostent *he_ptr = NULL; size_t buf_len = 1024; int result = -1; int h_errnop; int ret = 0; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_push(gg_gethostbyname_cleaner, &buf); if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif buf = malloc(buf_len); #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (buf != NULL) { #ifndef sun while ((ret = gethostbyname_r(hostname, &he, buf, buf_len, &he_ptr, &h_errnop)) == ERANGE) { #else while (((he_ptr = gethostbyname_r(hostname, &he, buf, buf_len, &h_errnop)) == NULL) && (errno == ERANGE)) { #endif buf_len *= 2; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif new_buf = realloc(buf, buf_len); if (new_buf != NULL) buf = new_buf; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (new_buf == NULL) { ret = ENOMEM; break; } } if (ret == 0 && he_ptr != NULL) { memcpy(addr, he_ptr->h_addr, sizeof(struct in_addr)); result = 0; } #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(buf); buf = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif } #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_pop(1); #endif return result; #else struct hostent *he; he = gethostbyname(hostname); if (he == NULL) return -1; memcpy(addr, he->h_addr, sizeof(struct in_addr)); return 0; #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ } struct in_addr *gg_gethostbyname(const char *hostname) { struct in_addr *addr; if (!(addr = malloc(sizeof(struct in_addr)))) return NULL; if (gg_gethostbyname_real(hostname, addr, 0)) { free(addr); return NULL; } return addr; } /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_fork_data { int pid; /*< Identyfikator procesu */ }; static int gg_resolver_fork_start(SOCKET *fd, void **priv_data, const char *hostname) { struct gg_resolver_fork_data *data = NULL; struct in_addr addr; int new_errno; SOCKET pipes[2]; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() invalid arguments\n"); errno = EFAULT; return -1; } data = malloc(sizeof(struct gg_resolver_fork_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() out of memory for resolver data\n"); return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->pid = fork(); if (data->pid == -1) { new_errno = errno; goto cleanup; } if (data->pid == 0) { gg_sock_close(pipes[0]); if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ gg_gethostbyname_real(hostname, &addr, 0); } if (gg_sock_write(pipes[1], &addr, sizeof(addr)) != sizeof(addr)) exit(1); exit(0); } gg_sock_close(pipes[1]); gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: free(data); gg_sock_close(pipes[0]); gg_sock_close(pipes[1]); errno = new_errno; return -1; } static void gg_resolver_fork_cleanup(void **priv_data, int force) { struct gg_resolver_fork_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_fork_data*) *priv_data; *priv_data = NULL; if (force) kill(data->pid, SIGKILL); waitpid(data->pid, NULL, WNOHANG); free(data); } #ifdef GG_CONFIG_HAVE_PTHREAD /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_pthread_data { pthread_t thread; /*< Identyfikator wątku */ char *hostname; /*< Nazwa serwera */ SOCKET rfd; /*< Deskryptor do odczytu */ SOCKET wfd; /*< Deskryptor do zapisu */ }; static void gg_resolver_pthread_cleanup(void **priv_data, int force) { struct gg_resolver_pthread_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_pthread_data *) *priv_data; *priv_data = NULL; if (force) { pthread_cancel(&data->thread); pthread_join(&data->thread, NULL); } free(data->hostname); data->hostname = NULL; if (data->wfd != -1) { gg_sock_close(data->wfd); data->wfd = -1; } free(data); } static void *__stdcall gg_resolver_pthread_thread(void *arg) { struct gg_resolver_pthread_data *data = arg; struct in_addr addr; pthread_detach(pthread_self()); if ((addr.s_addr = inet_addr(data->hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ gg_gethostbyname_real(data->hostname, &addr, 1); } if (gg_sock_write(data->wfd, &addr, sizeof(addr)) == sizeof(addr)) pthread_exit(NULL); else pthread_exit((void*) -1); return NULL; /* żeby kompilator nie marudził */ } static int gg_resolver_pthread_start(SOCKET *fd, void **priv_data, const char *hostname) { struct gg_resolver_pthread_data *data = NULL; int new_errno; SOCKET pipes[2]; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_pthread_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() invalid arguments\n"); errno = EFAULT; return -1; } data = malloc(sizeof(struct gg_resolver_pthread_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory for resolver data\n"); return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->hostname = strdup(hostname); if (data->hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory\n"); new_errno = errno; goto cleanup; } data->rfd = pipes[0]; data->wfd = pipes[1]; if (pthread_create(&data->thread, NULL, gg_resolver_pthread_thread, data)) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create thread\n"); new_errno = errno; goto cleanup; } gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: if (data) { free(data->hostname); free(data); } gg_sock_close(pipes[0]); gg_sock_close(pipes[1]); errno = new_errno; return -1; } #endif /* GG_CONFIG_HAVE_PTHREAD */ int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type) { if (gs == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gs->resolver_type = gg_global_resolver_type; gs->resolver_start = gg_global_resolver_start; gs->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) type = GG_RESOLVER_FORK; #else type = GG_RESOLVER_PTHREAD; #endif } switch (type) { case GG_RESOLVER_FORK: gs->resolver_type = type; gs->resolver_start = gg_resolver_fork_start; gs->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gs->resolver_type = type; gs->resolver_start = gg_resolver_pthread_start; gs->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } gg_resolver_t gg_session_get_resolver(struct gg_session *gs) { if (gs == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gs->resolver_type; } int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gs == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gs->resolver_type = GG_RESOLVER_CUSTOM; gs->resolver_start = resolver_start; gs->resolver_cleanup = resolver_cleanup; return 0; } int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type) { if (gh == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gh->resolver_type = gg_global_resolver_type; gh->resolver_start = gg_global_resolver_start; gh->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) type = GG_RESOLVER_FORK; #else type = GG_RESOLVER_PTHREAD; #endif } switch (type) { case GG_RESOLVER_FORK: gh->resolver_type = type; gh->resolver_start = gg_resolver_fork_start; gh->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gh->resolver_type = type; gh->resolver_start = gg_resolver_pthread_start; gh->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } gg_resolver_t gg_http_get_resolver(struct gg_http *gh) { if (gh == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gh->resolver_type; } int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gh == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gh->resolver_type = GG_RESOLVER_CUSTOM; gh->resolver_start = resolver_start; gh->resolver_cleanup = resolver_cleanup; return 0; } int gg_global_set_resolver(gg_resolver_t type) { switch (type) { case GG_RESOLVER_DEFAULT: gg_global_resolver_type = type; gg_global_resolver_start = NULL; gg_global_resolver_cleanup = NULL; return 0; case GG_RESOLVER_FORK: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_fork_start; gg_global_resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_pthread_start; gg_global_resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } gg_resolver_t gg_global_get_resolver(void) { return gg_global_resolver_type; } int gg_global_set_custom_resolver(int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gg_global_resolver_type = GG_RESOLVER_CUSTOM; gg_global_resolver_start = resolver_start; gg_global_resolver_cleanup = resolver_cleanup; return 0; }
/** * Rozpoczyna połączenie HTTP. * * Funkcja przeprowadza połączenie HTTP przy połączeniu synchronicznym, * zwracając wynik w polach struktury \c gg_http, lub błąd, gdy sesja się * nie powiedzie. * * Przy połączeniu asynchronicznym, funkcja rozpoczyna połączenie, a dalsze * etapy będą przeprowadzane po wykryciu zmian (\c watch) na obserwowanym * deskryptorze (\c fd) i wywołaniu funkcji \c gg_http_watch_fd(). * * Po zakończeniu, należy zwolnić strukturę za pomocą funkcji * \c gg_http_free(). Połączenie asynchroniczne można zatrzymać w każdej * chwili za pomocą \c gg_http_stop(). * * \param hostname Adres serwera * \param port Port serwera * \param async Flaga asynchronicznego połączenia * \param method Metoda HTTP * \param path Ścieżka do zasobu (musi być poprzedzona znakiem '/') * \param header Nagłówek zapytania plus ewentualne dane dla POST * * \return Zaalokowana struktura \c gg_http lub NULL, jeśli wystąpił błąd. * * \ingroup http */ struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header) { struct gg_http *h; if (!hostname || !port || !method || !path || !header) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() invalid arguments\n"); errno = EFAULT; return NULL; } if (!(h = malloc(sizeof(*h)))) return NULL; memset(h, 0, sizeof(*h)); h->async = async; h->port = port; h->fd = -1; h->type = GG_SESSION_HTTP; gg_http_set_resolver(h, GG_RESOLVER_DEFAULT); if (gg_proxy_enabled) { char *auth = gg_proxy_auth(); h->query = gg_saprintf("%s http://%s:%d%s HTTP/1.0\r\n%s%s", method, hostname, port, path, (auth) ? auth : "", header); hostname = gg_proxy_host; h->port = port = gg_proxy_port; free(auth); } else { h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s", method, path, header); } if (!h->query) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() not enough memory for query\n"); free(h); errno = ENOMEM; return NULL; } gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", h->query); if (async) { if (h->resolver_start(&h->fd, &h->resolver, hostname) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver failed\n"); gg_http_free(h); errno = ENOENT; return NULL; } gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver = %p\n", h->resolver); h->state = GG_STATE_RESOLVING; h->check = GG_CHECK_READ; h->timeout = GG_DEFAULT_TIMEOUT; } else { struct in_addr *addr_list = NULL; int addr_count; if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, 0) == -1 || addr_count == 0) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n"); gg_http_free(h); free(addr_list); errno = ENOENT; return NULL; } if ((h->fd = gg_connect(&addr_list[0], port, 0)) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno)); gg_http_free(h); free(addr_list); return NULL; } free(addr_list); h->state = GG_STATE_CONNECTING; while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) { if (gg_http_watch_fd(h) == -1) break; } if (h->state != GG_STATE_PARSING) { gg_debug(GG_DEBUG_MISC, "// gg_http_connect() some strange error\n"); gg_http_free(h); return NULL; } } h->callback = gg_http_watch_fd; h->destroy = gg_http_free; return h; }
/** * \internal Rozwiązuje nazwę i zapisuje wynik do podanego gniazda. * * \note Użycie logowania w tej funkcji może mieć negatywny wpływ na * aplikacje jednowątkowe korzystające. * * \param fd Deskryptor gniazda * \param hostname Nazwa serwera * \param pthread Flaga blokowania unicestwiania wątku podczas alokacji pamięci * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_resolver_run(int fd, const char *hostname, int pthread) { struct in_addr addr_ip[2], *addr_list = NULL; unsigned int addr_count; int res; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_push(gg_resolver_cleaner, &addr_list); #endif res = 0; if ((addr_ip[0].s_addr = inet_addr(hostname)) == INADDR_NONE) { if (gg_gethostbyname_real(hostname, &addr_list, &addr_count, pthread) == -1) { #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(addr_list); addr_list = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif addr_count = 0; /* addr_ip[0] już zawiera INADDR_NONE */ } } else { addr_ip[1].s_addr = INADDR_NONE; addr_count = 1; } if (send(fd, addr_list != NULL ? addr_list : addr_ip, (addr_count + 1) * sizeof(struct in_addr), 0) != (int)((addr_count + 1) * sizeof(struct in_addr))) { res = -1; } #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(addr_list); addr_list = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); pthread_cleanup_pop(0); #endif return res; }
/** * \internal Odpowiednik \c gethostbyname zapewniający współbieżność. * * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli * nie, to zwykłej \c gethostbyname. * * \param hostname Nazwa serwera * \param addr Wskaźnik na rezultat rozwiązywania nazwy * \param pthread Flaga blokowania unicestwiania wątku podczas alokacji pamięci * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_gethostbyname_real(const char *hostname, struct in_addr *addr, int pthread) { #ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R char *buf = NULL; char *new_buf = NULL; struct hostent he; struct hostent *he_ptr = NULL; size_t buf_len = 1024; int result = -1; int h_errnop; int ret = 0; #ifdef GG_CONFIG_HAVE_PTHREAD int old_state; #endif #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_push(gg_gethostbyname_cleaner, &buf); if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif buf = malloc(buf_len); #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (buf != NULL) { #ifndef sun while ((ret = gethostbyname_r(hostname, &he, buf, buf_len, &he_ptr, &h_errnop)) == ERANGE) { #else while (((he_ptr = gethostbyname_r(hostname, &he, buf, buf_len, &h_errnop)) == NULL) && (errno == ERANGE)) { #endif buf_len *= 2; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif new_buf = realloc(buf, buf_len); if (new_buf != NULL) buf = new_buf; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif if (new_buf == NULL) { ret = ENOMEM; break; } } if (ret == 0 && he_ptr != NULL) { memcpy(addr, he_ptr->h_addr, sizeof(struct in_addr)); result = 0; } #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); #endif free(buf); buf = NULL; #ifdef GG_CONFIG_HAVE_PTHREAD if (pthread) pthread_setcancelstate(old_state, NULL); #endif } #ifdef GG_CONFIG_HAVE_PTHREAD pthread_cleanup_pop(1); #endif return result; #else struct hostent *he; he = gethostbyname(hostname); if (he == NULL) return -1; memcpy(addr, he->h_addr, sizeof(struct in_addr)); return 0; #endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ } /** * \internal Odpowiednik \c gethostbyname zapewniający współbieżność. * * Jeśli dany system dostarcza \c gethostbyname_r, używa się tej wersji, jeśli * nie, to zwykłej \c gethostbyname. * * \param hostname Nazwa serwera * * \return Zaalokowana struktura \c in_addr lub NULL w przypadku błędu. */ struct in_addr *gg_gethostbyname(const char *hostname) { struct in_addr *addr; if (!(addr = (in_addr*)malloc(sizeof(struct in_addr)))) return NULL; if (gg_gethostbyname_real(hostname, addr, 0)) { free(addr); return NULL; } return addr; } /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_fork_data { int pid; /*< Identyfikator procesu */ }; /** * \internal Rozwiązuje nazwę serwera w osobnym procesie. * * Połączenia asynchroniczne nie mogą blokować procesu w trakcie rozwiązywania * nazwy serwera. W tym celu tworzony jest potok, nowy proces i dopiero w nim * przeprowadzane jest rozwiązywanie nazwy. Deskryptor strony do odczytu * zapisuje się w strukturze sieci i czeka na dane w postaci struktury * \c in_addr. Jeśli nie znaleziono nazwy, zwracana jest \c INADDR_NONE. * * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor * potoku * \param priv_data Wskaźnik na zmienną, gdzie zostanie umieszczony wskaźnik * do numeru procesu potomnego rozwiązującego nazwę * \param hostname Nazwa serwera do rozwiązania * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_resolver_fork_start(SOCKET *fd, void **priv_data, const char *hostname) { struct gg_resolver_fork_data *data = NULL; struct in_addr addr; int new_errno; SOCKET pipes[2]; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() invalid arguments\n"); errno = EFAULT; return -1; } data = (gg_resolver_fork_data*)malloc(sizeof(struct gg_resolver_fork_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() out of memory for resolver data\n"); return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->pid = fork(); if (data->pid == -1) { new_errno = errno; goto cleanup; } if (data->pid == 0) { gg_sock_close(pipes[0]); if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ gg_gethostbyname_real(hostname, &addr, 0); } if (gg_sock_write(pipes[1], &addr, sizeof(addr)) != sizeof(addr)) exit(1); exit(0); } gg_sock_close(pipes[1]); gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: free(data); gg_sock_close(pipes[0]); gg_sock_close(pipes[1]); errno = new_errno; return -1; } /** * \internal Usuwanie zasobów po procesie rozwiązywaniu nazwy. * * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu * zasobów sesji podczas rozwiązywania nazwy. * * \param priv_data Wskaźnik na zmienną przechowującą wskaźnik do prywatnych * danych * \param force Flaga usuwania zasobów przed zakończeniem działania */ static void gg_resolver_fork_cleanup(void **priv_data, int force) { struct gg_resolver_fork_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_fork_data*) *priv_data; *priv_data = NULL; if (force) kill(data->pid, SIGKILL); waitpid(data->pid, NULL, WNOHANG); free(data); } #ifdef GG_CONFIG_HAVE_PTHREAD /** * \internal Struktura przekazywana do wątku rozwiązującego nazwę. */ struct gg_resolver_pthread_data { pthread_t thread; /*< Identyfikator wątku */ char *hostname; /*< Nazwa serwera */ SOCKET rfd; /*< Deskryptor do odczytu */ SOCKET wfd; /*< Deskryptor do zapisu */ }; /** * \internal Usuwanie zasobów po wątku rozwiązywaniu nazwy. * * Funkcja wywoływana po zakończeniu rozwiązanywania nazwy lub przy zwalnianiu * zasobów sesji podczas rozwiązywania nazwy. * * \param priv_data Wskaźnik na zmienną przechowującą wskaźnik do prywatnych * danych * \param force Flaga usuwania zasobów przed zakończeniem działania */ static void gg_resolver_pthread_cleanup(void **priv_data, int force) { struct gg_resolver_pthread_data *data; if (priv_data == NULL || *priv_data == NULL) return; data = (struct gg_resolver_pthread_data *) *priv_data; *priv_data = NULL; if (force) { pthread_cancel(&data->thread); pthread_join(&data->thread, NULL); } free(data->hostname); data->hostname = NULL; if (data->wfd != -1) { gg_sock_close(data->wfd); data->wfd = -1; } free(data); } /** * \internal Wątek rozwiązujący nazwę. * * \param arg Wskaźnik na strukturę \c gg_resolver_pthread_data */ static void *__stdcall gg_resolver_pthread_thread(void *arg) { struct gg_resolver_pthread_data *data = (gg_resolver_pthread_data*)arg; struct in_addr addr; pthread_detach(pthread_self()); if ((addr.s_addr = inet_addr(data->hostname)) == INADDR_NONE) { /* W przypadku błędu gg_gethostbyname_real() zwróci -1 * i nie zmieni &addr. Tam jest już INADDR_NONE, * więc nie musimy robić nic więcej. */ gg_gethostbyname_real(data->hostname, &addr, 1); } if (gg_sock_write(data->wfd, &addr, sizeof(addr)) == sizeof(addr)) pthread_exit(NULL); else pthread_exit((void*) -1); return NULL; /* żeby kompilator nie marudził */ } /** * \internal Rozwiązuje nazwę serwera w osobnym wątku. * * Funkcja działa analogicznie do \c gg_resolver_fork_start(), z tą różnicą, * że działa na wątkach, nie procesach. Jest dostępna wyłącznie gdy podczas * kompilacji włączono odpowiednią opcję. * * \param fd Wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor * potoku * \param priv_data Wskaźnik na zmienną, gdzie zostanie umieszczony wskaźnik * do prywatnych danych wątku rozwiązującego nazwę * \param hostname Nazwa serwera do rozwiązania * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ static int gg_resolver_pthread_start(SOCKET *fd, void **priv_data, const char *hostname) { struct gg_resolver_pthread_data *data = NULL; int new_errno; SOCKET pipes[2]; gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_pthread_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); if (fd == NULL || priv_data == NULL || hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() invalid arguments\n"); errno = EFAULT; return -1; } data = (gg_resolver_pthread_data*)malloc(sizeof(struct gg_resolver_pthread_data)); if (data == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory for resolver data\n"); return -1; } if (pipe(pipes) == -1) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); free(data); return -1; } data->hostname = strdup(hostname); if (data->hostname == NULL) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory\n"); new_errno = errno; goto cleanup; } data->rfd = pipes[0]; data->wfd = pipes[1]; if (pthread_create(&data->thread, NULL, gg_resolver_pthread_thread, data)) { gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create thread\n"); new_errno = errno; goto cleanup; } gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() %p\n", data); *fd = pipes[0]; *priv_data = data; return 0; cleanup: if (data) { free(data->hostname); free(data); } gg_sock_close(pipes[0]); gg_sock_close(pipes[1]); errno = new_errno; return -1; } #endif /* GG_CONFIG_HAVE_PTHREAD */ /** * Ustawia sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type) { if (gs == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gs->resolver_type = gg_global_resolver_type; gs->resolver_start = gg_global_resolver_start; gs->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) type = GG_RESOLVER_FORK; #else type = GG_RESOLVER_PTHREAD; #endif } switch (type) { case GG_RESOLVER_FORK: gs->resolver_type = type; gs->resolver_start = gg_resolver_fork_start; gs->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gs->resolver_type = type; gs->resolver_start = gg_resolver_pthread_start; gs->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_session_get_resolver(struct gg_session *gs) { if (gs == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gs->resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw w sesji. * * \param gs Struktura sesji * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gs == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gs->resolver_type = GG_RESOLVER_CUSTOM; gs->resolver_start = resolver_start; gs->resolver_cleanup = resolver_cleanup; return 0; } /** * Ustawia sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura połączenia * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type) { if (gh == NULL) { errno = EINVAL; return -1; } if (type == GG_RESOLVER_DEFAULT) { if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { gh->resolver_type = gg_global_resolver_type; gh->resolver_start = gg_global_resolver_start; gh->resolver_cleanup = gg_global_resolver_cleanup; return 0; } #if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) type = GG_RESOLVER_FORK; #else type = GG_RESOLVER_PTHREAD; #endif } switch (type) { case GG_RESOLVER_FORK: gh->resolver_type = type; gh->resolver_start = gg_resolver_fork_start; gh->resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gh->resolver_type = type; gh->resolver_start = gg_resolver_pthread_start; gh->resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura połączenia * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_http_get_resolver(struct gg_http *gh) { if (gh == NULL) { errno = EINVAL; return GG_RESOLVER_INVALID; } return gh->resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw połączenia HTTP. * * \param gh Struktura sesji * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (gh == NULL || resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gh->resolver_type = GG_RESOLVER_CUSTOM; gh->resolver_start = resolver_start; gh->resolver_cleanup = resolver_cleanup; return 0; } /** * Ustawia sposób rozwiązywania nazw globalnie dla biblioteki. * * \param type Sposób rozwiązywania nazw (patrz \ref build-resolver) * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_global_set_resolver(gg_resolver_t type) { switch (type) { case GG_RESOLVER_DEFAULT: gg_global_resolver_type = type; gg_global_resolver_start = NULL; gg_global_resolver_cleanup = NULL; return 0; case GG_RESOLVER_FORK: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_fork_start; gg_global_resolver_cleanup = gg_resolver_fork_cleanup; return 0; #ifdef GG_CONFIG_HAVE_PTHREAD case GG_RESOLVER_PTHREAD: gg_global_resolver_type = type; gg_global_resolver_start = gg_resolver_pthread_start; gg_global_resolver_cleanup = gg_resolver_pthread_cleanup; return 0; #endif default: errno = EINVAL; return -1; } } /** * Zwraca sposób rozwiązywania nazw globalnie dla biblioteki. * * \return Sposób rozwiązywania nazw */ gg_resolver_t gg_global_get_resolver(void) { return gg_global_resolver_type; } /** * Ustawia własny sposób rozwiązywania nazw globalnie dla biblioteki. * * \param resolver_start Funkcja rozpoczynająca rozwiązywanie nazwy * \param resolver_cleanup Funkcja zwalniająca zasoby * * Parametry funkcji rozpoczynającej rozwiązywanie nazwy wyglądają następująco: * - \c "SOCKET *fd" — wskaźnik na zmienną, gdzie zostanie umieszczony deskryptor potoku * - \c "void **priv_data" — wskaźnik na zmienną, gdzie można umieścić wskaźnik do prywatnych danych na potrzeby rozwiązywania nazwy * - \c "const char *name" — nazwa serwera do rozwiązania * * Parametry funkcji zwalniającej zasoby wyglądają następująco: * - \c "void **priv_data" — wskaźnik na zmienną przechowującą wskaźnik do prywatnych danych, należy go ustawić na \c NULL po zakończeniu * - \c "int force" — flaga mówiąca o tym, że zasoby są zwalniane przed zakończeniem rozwiązywania nazwy, np. z powodu zamknięcia sesji. * * Własny kod rozwiązywania nazwy powinien stworzyć potok, parę gniazd lub * inny deskryptor pozwalający na co najmniej jednostronną komunikację i * przekazać go w parametrze \c fd. Po zakończeniu rozwiązywania nazwy, * powinien wysłać otrzymany adres IP w postaci sieciowej (big-endian) do * deskryptora. Jeśli rozwiązywanie nazwy się nie powiedzie, należy wysłać * \c INADDR_NONE. Następnie zostanie wywołana funkcja zwalniająca zasoby * z parametrem \c force równym \c 0. Gdyby sesja została zakończona przed * rozwiązaniem nazwy, np. za pomocą funkcji \c gg_logoff(), funkcja * zwalniająca zasoby zostanie wywołana z parametrem \c force równym \c 1. * * \return 0 jeśli się powiodło, -1 w przypadku błędu */ int gg_global_set_custom_resolver(int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) { if (resolver_start == NULL || resolver_cleanup == NULL) { errno = EINVAL; return -1; } gg_global_resolver_type = GG_RESOLVER_CUSTOM; gg_global_resolver_start = resolver_start; gg_global_resolver_cleanup = resolver_cleanup; return 0; }