diff options
-rw-r--r-- | include/git2/sys/stream.h | 40 | ||||
-rw-r--r-- | src/socket_stream.c | 219 | ||||
-rw-r--r-- | src/stream.h | 48 |
3 files changed, 307 insertions, 0 deletions
diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h new file mode 100644 index 000000000..69f8554da --- /dev/null +++ b/include/git2/sys/stream.h @@ -0,0 +1,40 @@ +/* + * 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. + */ +#ifndef INCLUDE_sys_git_stream_h__ +#define INCLUDE_sys_git_stream_h__ + +#include "git2/common.h" +#include "git2/types.h" + +GIT_BEGIN_DECL + +#define GIT_STREAM_VERSION 1 + +/** + * Every stream must have this struct as its first element, so the + * API can talk to it. You'd define your stream as + * + * struct my_stream { + * git_stream parent; + * ... + * } + * + * and fill the functions + */ +typedef struct git_stream { + int version; + + int encrypted; + int (*connect)(struct git_stream *); + int (*certificate)(git_cert **, struct git_stream *); + ssize_t (*read)(struct git_stream *, void *, size_t); + ssize_t (*write)(struct git_stream *, void *, size_t, int); + int (*close)(struct git_stream *); + void (*free)(struct git_stream *); +} git_stream; + +#endif diff --git a/src/socket_stream.c b/src/socket_stream.c new file mode 100644 index 000000000..c182d6d6c --- /dev/null +++ b/src/socket_stream.c @@ -0,0 +1,219 @@ +/* + * 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 "common.h" +#include "posix.h" +#include "stream.h" +#include "netops.h" + +#ifndef _WIN32 +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/select.h> +# include <sys/time.h> +# include <netdb.h> +# include <netinet/in.h> +# include <arpa/inet.h> +#else +# include <winsock2.h> +# include <ws2tcpip.h> +# 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) { + giterr_set(GITERR_NET, "%s: %s", str, win32_error); + git__free(win32_error); + } else { + giterr_set(GITERR_NET, str); + } +} +#else +static void net_set_error(const char *str) +{ + giterr_set(GITERR_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()) { + giterr_set(GITERR_OS, "Winsock cleanup failed"); + return -1; + } + + return 0; +#else + return close(s); +#endif + +} + +typedef struct { + git_stream parent; + char *host; + char *port; + GIT_SOCKET s; +} socket_stream; + +int socket_connect(git_stream *stream) +{ + struct addrinfo *info = NULL, *p; + struct addrinfo hints; + socket_stream *st = (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) { + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } + + if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { + WSACleanup(); + giterr_set(GITERR_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) { + giterr_set(GITERR_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, p->ai_protocol); + + if (s == INVALID_SOCKET) { + net_set_error("error creating socket"); + break; + } + + 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) { + giterr_set(GITERR_OS, "Failed to connect to %s", st->host); + p_freeaddrinfo(info); + return -1; + } + + st->s = s; + p_freeaddrinfo(info); + return 0; +} + +ssize_t socket_write(git_stream *stream, void *data, size_t len, int flags) +{ + ssize_t ret; + size_t off = 0; + socket_stream *st = (socket_stream *) stream; + + while (off < len) { + errno = 0; + ret = p_send(st->s, data + off, len - off, flags); + if (ret < 0) { + net_set_error("Error sending data"); + return -1; + } + + off += ret; + } + + return off; +} + +ssize_t socket_read(git_stream *stream, void *data, size_t len) +{ + ssize_t ret; + socket_stream *st = (socket_stream *) stream; + + if ((ret = p_recv(st->s, data, len, 0)) < 0) + net_set_error("Error receiving socket data"); + + return ret; +} + +int socket_close(git_stream *stream) +{ + socket_stream *st = (socket_stream *) stream; + int error; + + error = close_socket(st->s); + st->s = INVALID_SOCKET; + + return error; +} + +void socket_free(git_stream *stream) +{ + socket_stream *st = (socket_stream *) stream; + + git__free(st->host); + git__free(st->port); + git__free(st); +} + +int git_socket_stream_new(git_stream **out, const char *host, const char *port) +{ + socket_stream *st; + + assert(out && host); + + st = git__calloc(1, sizeof(socket_stream)); + GITERR_CHECK_ALLOC(st); + + st->host = git__strdup(host); + GITERR_CHECK_ALLOC(st->host); + + if (port) { + st->port = git__strdup(port); + GITERR_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; +} diff --git a/src/stream.h b/src/stream.h new file mode 100644 index 000000000..8eec83009 --- /dev/null +++ b/src/stream.h @@ -0,0 +1,48 @@ +/* + * 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. + */ +#ifndef INCLUDE_stream_h__ +#define INCLUDE_stream_h__ + +#include "common.h" +#include "git2/sys/stream.h" + +GIT_INLINE(int) git_stream_connect(git_stream *st) +{ + return st->connect(st); +} + +GIT_INLINE(int) git_stream_certificate(git_cert **out, git_stream *st) +{ + if (!st->encrypted) { + giterr_set(GITERR_INVALID, "an unencrypted stream does not have a certificate"); + return -1; + } + + return st->certificate(out, st); +} + +GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len) +{ + return st->read(st, data, len); +} + +GIT_INLINE(ssize_t) git_stream_write(git_stream *st, void *data, size_t len, int flags) +{ + return st->write(st, data, len, flags); +} + +GIT_INLINE(int) git_stream_close(git_stream *st) +{ + return st->close(st); +} + +GIT_INLINE(void) git_stream_free(git_stream *st) +{ + return st->free(st); +} + +#endif |