root/src/system/libnetwork/socket.cpp
/*
 * Copyright 2002-2008, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 */

/*!
        The socket API directly forwards all requests into the kernel stack
        via the networking stack driver.
*/

#include <r5_compatibility.h>

#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include <syscall_utils.h>

#include <syscalls.h>


static void
convert_from_r5_sockaddr(struct sockaddr *_to, const struct sockaddr *_from)
{
        const r5_sockaddr_in *from = (r5_sockaddr_in *)_from;
        sockaddr_in *to = (sockaddr_in *)_to;

        memset(to, 0, sizeof(sockaddr));
        to->sin_len = sizeof(sockaddr);

        if (from == NULL)
                return;

        if (from->sin_family == R5_AF_INET)
                to->sin_family = AF_INET;
        else
                to->sin_family = from->sin_family;

        to->sin_port = from->sin_port;
        to->sin_addr.s_addr = from->sin_addr;
}


static void
convert_to_r5_sockaddr(struct sockaddr *_to,
        const struct sockaddr *_from)
{
        const sockaddr_in *from = (sockaddr_in *)_from;
        r5_sockaddr_in *to = (r5_sockaddr_in *)_to;

        if (to == NULL)
                return;

        memset(to, 0, sizeof(r5_sockaddr_in));

        if (from->sin_family == AF_INET)
                to->sin_family = R5_AF_INET;
        else
                to->sin_family = from->sin_family;

        to->sin_port = from->sin_port;
        to->sin_addr = from->sin_addr.s_addr;
}


static void
convert_from_r5_socket(int& family, int& type, int& protocol)
{
        switch (family) {
                case R5_AF_INET:
                        family = AF_INET;
                        break;
        }

        switch (type) {
                case R5_SOCK_DGRAM:
                        type = SOCK_DGRAM;
                        break;
                case R5_SOCK_STREAM:
                        type = SOCK_STREAM;
                        break;
#if 0
                case R5_SOCK_RAW:
                        type = SOCK_RAW;
                        break;
#endif
        }

        switch (protocol) {
                case R5_IPPROTO_UDP:
                        protocol = IPPROTO_UDP;
                        break;
                case R5_IPPROTO_TCP:
                        protocol = IPPROTO_TCP;
                        break;
                case R5_IPPROTO_ICMP:
                        protocol = IPPROTO_ICMP;
                        break;
        }
}


static void
convert_from_r5_sockopt(int& level, int& option)
{
        if (level == R5_SOL_SOCKET)
                level = SOL_SOCKET;

        switch (option) {
                case R5_SO_DEBUG:
                        option = SO_DEBUG;
                        break;
                case R5_SO_REUSEADDR:
                        option = SO_REUSEADDR;
                        break;
                case R5_SO_NONBLOCK:
                        option = SO_NONBLOCK;
                        break;
                case R5_SO_REUSEPORT:
                        option = SO_REUSEPORT;
                        break;
                case R5_SO_FIONREAD:
                        // there is no SO_FIONREAD
                        option = -1;
                        break;
        }
}


// #pragma mark -


extern "C" int
socket(int family, int type, int protocol)
{
        if (check_r5_compatibility())
                convert_from_r5_socket(family, type, protocol);

        RETURN_AND_SET_ERRNO(_kern_socket(family, type, protocol));
}


extern "C" int
bind(int socket, const struct sockaddr *address, socklen_t addressLength)
{
        struct sockaddr haikuAddr;

        if (check_r5_compatibility()) {
                convert_from_r5_sockaddr(&haikuAddr, address);
                address = &haikuAddr;
                addressLength = sizeof(struct sockaddr_in);
        }

        RETURN_AND_SET_ERRNO(_kern_bind(socket, address, addressLength));
}


extern "C" int
shutdown(int socket, int how)
{
        RETURN_AND_SET_ERRNO(_kern_shutdown_socket(socket, how));
}


extern "C" int
connect(int socket, const struct sockaddr *address, socklen_t addressLength)
{
        struct sockaddr haikuAddr;

        if (check_r5_compatibility()) {
                convert_from_r5_sockaddr(&haikuAddr, address);
                address = &haikuAddr;
                addressLength = sizeof(struct sockaddr_in);
        }

        RETURN_AND_SET_ERRNO_TEST_CANCEL(
                _kern_connect(socket, address, addressLength));
}


extern "C" int
listen(int socket, int backlog)
{
        RETURN_AND_SET_ERRNO(_kern_listen(socket, backlog));
}


extern "C" int
accept(int socket, struct sockaddr *_address, socklen_t *_addressLength)
{
        return accept4(socket, _address, _addressLength, 0);
}


extern "C" int
accept4(int socket, struct sockaddr *_address, socklen_t *_addressLength, int flags)
{
        bool r5compatible = check_r5_compatibility();
        struct sockaddr haikuAddr;

        sockaddr* address;
        socklen_t addressLength;

        if (r5compatible && _address != NULL) {
                address = &haikuAddr;
                addressLength = sizeof(haikuAddr);
        } else {
                address = _address;
                addressLength = _addressLength ? *_addressLength : 0;
        }

        int acceptSocket = _kern_accept(socket, address, &addressLength, flags);

        pthread_testcancel();

        if (acceptSocket < 0) {
                errno = acceptSocket;
                return -1;
        }

        if (r5compatible && _address != NULL) {
                convert_to_r5_sockaddr(_address, &haikuAddr);
                if (_addressLength != NULL)
                        *_addressLength = sizeof(struct r5_sockaddr_in);
        } else if (_addressLength != NULL)
                *_addressLength = addressLength;

        return acceptSocket;
}


extern "C" ssize_t
recv(int socket, void *data, size_t length, int flags)
{
        RETURN_AND_SET_ERRNO_TEST_CANCEL(_kern_recv(socket, data, length, flags));
}


extern "C" ssize_t
recvfrom(int socket, void *data, size_t length, int flags,
        struct sockaddr *_address, socklen_t *_addressLength)
{
        bool r5compatible = check_r5_compatibility();
        struct sockaddr haikuAddr;

        sockaddr* address;
        socklen_t addressLength;

        if (r5compatible && _address != NULL) {
                address = &haikuAddr;
                addressLength = sizeof(haikuAddr);
        } else {
                address = _address;
                addressLength = _addressLength ? *_addressLength : 0;
        }

        ssize_t bytesReceived = _kern_recvfrom(socket, data, length, flags,
                address, &addressLength);

        pthread_testcancel();

        if (bytesReceived < 0) {
                errno = bytesReceived;
                return -1;
        }

        if (r5compatible) {
                convert_to_r5_sockaddr(_address, &haikuAddr);
                if (_addressLength != NULL)
                        *_addressLength = sizeof(struct r5_sockaddr_in);
        } else if (_addressLength != NULL)
                *_addressLength = addressLength;

        return bytesReceived;
}


extern "C" ssize_t
recvmsg(int socket, struct msghdr *message, int flags)
{
        RETURN_AND_SET_ERRNO_TEST_CANCEL(_kern_recvmsg(socket, message, flags));
}


extern "C" ssize_t
send(int socket, const void *data, size_t length, int flags)
{
        RETURN_AND_SET_ERRNO_TEST_CANCEL(_kern_send(socket, data, length, flags));
}


extern "C" ssize_t
sendto(int socket, const void *data, size_t length, int flags,
        const struct sockaddr *address, socklen_t addressLength)
{
        struct sockaddr haikuAddr;

        if (check_r5_compatibility()) {
                convert_from_r5_sockaddr(&haikuAddr, address);
                address = &haikuAddr;
                addressLength = sizeof(struct sockaddr_in);
        }

        RETURN_AND_SET_ERRNO_TEST_CANCEL(
                _kern_sendto(socket, data, length, flags, address, addressLength));
}


extern "C" ssize_t
sendmsg(int socket, const struct msghdr *message, int flags)
{
        RETURN_AND_SET_ERRNO_TEST_CANCEL(_kern_sendmsg(socket, message, flags));
}


extern "C" int
getsockopt(int socket, int level, int option, void *value, socklen_t *_length)
{
        if (check_r5_compatibility()) {
                if (option == R5_SO_FIONREAD) {
                        // there is no SO_FIONREAD in our stack; we're using FIONREAD
                        // instead
                        *_length = sizeof(int);
                        return ioctl(socket, FIONREAD, value);
                }

                convert_from_r5_sockopt(level, option);
        }

        RETURN_AND_SET_ERRNO(_kern_getsockopt(socket, level, option, value,
                _length));
}


extern "C" int
setsockopt(int socket, int level, int option, const void *value,
        socklen_t length)
{
        if (check_r5_compatibility())
                convert_from_r5_sockopt(level, option);

        RETURN_AND_SET_ERRNO(_kern_setsockopt(socket, level, option, value,
                length));
}


extern "C" int
getpeername(int socket, struct sockaddr *_address, socklen_t *_addressLength)
{
        bool r5compatible = check_r5_compatibility();
        struct sockaddr haikuAddr;

        sockaddr* address;
        socklen_t addressLength;

        if (r5compatible && _address != NULL) {
                address = &haikuAddr;
                addressLength = sizeof(haikuAddr);
        } else {
                address = _address;
                addressLength = _addressLength ? *_addressLength : 0;
        }

        status_t error = _kern_getpeername(socket, address, &addressLength);
        if (error != B_OK) {
                errno = error;
                return -1;
        }

        if (r5compatible) {
                convert_to_r5_sockaddr(_address, &haikuAddr);
                if (_addressLength != NULL)
                        *_addressLength = sizeof(struct r5_sockaddr_in);
        } else if (_addressLength != NULL)
                *_addressLength = addressLength;

        return 0;
}


extern "C" int
getsockname(int socket, struct sockaddr *_address, socklen_t *_addressLength)
{
        bool r5compatible = check_r5_compatibility();
        struct sockaddr haikuAddr;

        sockaddr* address;
        socklen_t addressLength;

        if (r5compatible && _address != NULL) {
                address = &haikuAddr;
                addressLength = sizeof(haikuAddr);
        } else {
                address = _address;
                addressLength = _addressLength ? *_addressLength : 0;
        }

        status_t error = _kern_getsockname(socket, address, &addressLength);
        if (error != B_OK) {
                errno = error;
                return -1;
        }

        if (r5compatible) {
                convert_to_r5_sockaddr(_address, &haikuAddr);
                if (_addressLength != NULL)
                        *_addressLength = sizeof(struct r5_sockaddr_in);
        } else if (_addressLength != NULL)
                *_addressLength = addressLength;

        return 0;
}


extern "C" int
sockatmark(int socket)
{
        RETURN_AND_SET_ERRNO(_kern_sockatmark(socket));
}


extern "C" int
socketpair(int family, int type, int protocol, int socketVector[2])
{
        RETURN_AND_SET_ERRNO(_kern_socketpair(family, type, protocol,
                socketVector));
}