mirror of
https://git.proxmox.com/git/libgit2
synced 2026-01-01 10:08:11 +00:00
236 lines
4.9 KiB
C
236 lines
4.9 KiB
C
/*
|
|
* 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 <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) {
|
|
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);
|
|
}
|