/* * Copyright (C) the libgit2 contributors. All rights reserved. * * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ #include "streams/socket.h" #include "posix.h" #include "netops.h" #include "registry.h" #include "stream.h" #ifndef _WIN32 # include # include # include # include # include # include # include #else # include # include # ifdef _MSC_VER # pragma comment(lib, "ws2_32") # endif #endif #ifdef GIT_WIN32 static void net_set_error(const char *str) { int error = WSAGetLastError(); char * win32_error = git_win32_get_error_message(error); if (win32_error) { git_error_set(GIT_ERROR_NET, "%s: %s", str, win32_error); git__free(win32_error); } else { git_error_set(GIT_ERROR_NET, "%s", str); } } #else static void net_set_error(const char *str) { git_error_set(GIT_ERROR_NET, "%s: %s", str, strerror(errno)); } #endif static int close_socket(GIT_SOCKET s) { if (s == INVALID_SOCKET) return 0; #ifdef GIT_WIN32 if (SOCKET_ERROR == closesocket(s)) return -1; if (0 != WSACleanup()) { git_error_set(GIT_ERROR_OS, "winsock cleanup failed"); return -1; } return 0; #else return close(s); #endif } static int socket_connect(git_stream *stream) { struct addrinfo *info = NULL, *p; struct addrinfo hints; git_socket_stream *st = (git_socket_stream *) stream; GIT_SOCKET s = INVALID_SOCKET; int ret; #ifdef GIT_WIN32 /* on win32, the WSA context needs to be initialized * before any socket calls can be performed */ WSADATA wsd; if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { git_error_set(GIT_ERROR_OS, "winsock init failed"); return -1; } if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { WSACleanup(); git_error_set(GIT_ERROR_OS, "winsock init failed"); return -1; } #endif memset(&hints, 0x0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; if ((ret = p_getaddrinfo(st->host, st->port, &hints, &info)) != 0) { git_error_set(GIT_ERROR_NET, "failed to resolve address for %s: %s", st->host, p_gai_strerror(ret)); return -1; } for (p = info; p != NULL; p = p->ai_next) { s = socket(p->ai_family, p->ai_socktype | SOCK_CLOEXEC, p->ai_protocol); if (s == INVALID_SOCKET) continue; if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0) break; /* If we can't connect, try the next one */ close_socket(s); s = INVALID_SOCKET; } /* Oops, we couldn't connect to any address */ if (s == INVALID_SOCKET && p == NULL) { git_error_set(GIT_ERROR_OS, "failed to connect to %s", st->host); p_freeaddrinfo(info); return -1; } st->s = s; p_freeaddrinfo(info); return 0; } static ssize_t socket_write(git_stream *stream, const char *data, size_t len, int flags) { git_socket_stream *st = (git_socket_stream *) stream; ssize_t written; errno = 0; if ((written = p_send(st->s, data, len, flags)) < 0) { net_set_error("error sending data"); return -1; } return written; } static ssize_t socket_read(git_stream *stream, void *data, size_t len) { ssize_t ret; git_socket_stream *st = (git_socket_stream *) stream; if ((ret = p_recv(st->s, data, len, 0)) < 0) net_set_error("error receiving socket data"); return ret; } static int socket_close(git_stream *stream) { git_socket_stream *st = (git_socket_stream *) stream; int error; error = close_socket(st->s); st->s = INVALID_SOCKET; return error; } static void socket_free(git_stream *stream) { git_socket_stream *st = (git_socket_stream *) stream; git__free(st->host); git__free(st->port); git__free(st); } static int default_socket_stream_new( git_stream **out, const char *host, const char *port) { git_socket_stream *st; assert(out && host && port); st = git__calloc(1, sizeof(git_socket_stream)); GIT_ERROR_CHECK_ALLOC(st); st->host = git__strdup(host); GIT_ERROR_CHECK_ALLOC(st->host); if (port) { st->port = git__strdup(port); GIT_ERROR_CHECK_ALLOC(st->port); } st->parent.version = GIT_STREAM_VERSION; st->parent.connect = socket_connect; st->parent.write = socket_write; st->parent.read = socket_read; st->parent.close = socket_close; st->parent.free = socket_free; st->s = INVALID_SOCKET; *out = (git_stream *) st; return 0; } int git_socket_stream_new( git_stream **out, const char *host, const char *port) { int (*init)(git_stream **, const char *, const char *) = NULL; git_stream_registration custom = {0}; int error; assert(out && host && port); if ((error = git_stream_registry_lookup(&custom, GIT_STREAM_STANDARD)) == 0) init = custom.init; else if (error == GIT_ENOTFOUND) init = default_socket_stream_new; else return error; if (!init) { git_error_set(GIT_ERROR_NET, "there is no socket stream available"); return -1; } return init(out, host, port); }