/*

Copyright (C) 2019 Olaf Till <i7tiol@t-online.de>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; If not, see <http://www.gnu.org/licenses/>.

*/

#define EXTERNAL_BINARY
#include "config.h"

#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <errno.h>
#include <signal.h>
#include <math.h>
#include <string.h>
#include <stdint.h>

int gnulib_close (int fd)
{
  return close (fd);
}

int gnulib_socket_pfinet_sockstream (int protocol)
{
  return socket (PF_INET, SOCK_STREAM, protocol);
}

ssize_t gnulib_recv (int sockfd, void *buf, size_t len, int flags)
{
  return recv (sockfd, buf, len, flags);
}

ssize_t gnulib_send (int sockfd, const void *buf, size_t len, int flags)
{
  return send (sockfd, buf, len, flags);
}

void *gnulib_malloc (size_t size)
{
  return malloc (size);
}

static
const struct addrinfo *get_canonname_hints (void)
{
  static struct addrinfo hints;

  if (! hints.ai_socktype) // ai_socktype == 0 means "any type", so it
                           // should be distinct from SOCK_STREAM
    {
      hints.ai_family = AF_INET;
      hints.ai_socktype = SOCK_STREAM;
      hints.ai_protocol = 0;
      hints.ai_flags = AI_CANONNAME;
    }

  return &hints;
}

static
const struct addrinfo *get_set_port_hints (void)
{
  static struct addrinfo hints;

  if (! hints.ai_socktype) // ai_socktype == 0 means "any type", so it
                           // should be distinct from SOCK_STREAM
    {
      hints.ai_family = AF_INET;
      hints.ai_socktype = SOCK_STREAM;
      hints.ai_protocol = 0;
      hints.ai_flags = 0;
    }

  return &hints;
}

inline static
struct addrinfo **get_ai (void)
{
  static struct addrinfo *ai;

  return &ai;
}

void gnulib_freeaddrinfo (void)
{
  freeaddrinfo (*get_ai ());
}

const char *gnulib_get_canonname (const char *hostname)
{
  struct addrinfo **ai_p = get_ai ();

  if (getaddrinfo (hostname, NULL, get_canonname_hints (), ai_p))
    return "";
  else
    return (*ai_p)->ai_canonname;
}

int gnulib_set_port (const char *hostname, const char *port)
{
  struct addrinfo **ai_p = get_ai ();

  if (getaddrinfo (hostname, port, get_set_port_hints (), ai_p))
    return 1;
  else
    return 0;
}

int gnulib_connect (int sockfd)
{
  struct addrinfo **ai_p = get_ai ();

  return connect (sockfd, (*ai_p)->ai_addr, (*ai_p)->ai_addrlen);
}

// 'poll'-related stuff

void *gnulib_alloc_pollfds (int n)
{
  return calloc (n, sizeof (struct pollfd));
}

int gnulib_poll_in_wrapper (int *in_out, void *pfds_arg, int nfds, int timeout)
{
  struct pollfd *pfds = pfds_arg;

  for (int id = 0; id < nfds; id++)
    {
      pfds[id].fd = in_out[id];

      pfds[id].events = POLLIN;

      pfds[id].revents = 0;
    }

  int ret;

  ret = poll (pfds, nfds, timeout);

  for (int id = 0; id < nfds; id++)
    {
      in_out[id] = pfds[id].revents;
    }

  return ret;
}

int gnulib_pollin_1_noblock (int fd)
{
  struct pollfd pfd;

  pfd.fd = fd;
  pfd.events = POLLIN;
  pfd.revents = 0;

  return poll (&pfd, 1, 0);
}

int gnulib_pollin_2_block (int fd1, int fd2, int *rev1, int *rev2)
{
  struct pollfd pfd[2];

  pfd[0].fd = fd1;
  pfd[0].events = POLLIN;
  pfd[0].revents = 0;

  pfd[1].fd = fd2;
  pfd[1].events = POLLIN;
  pfd[1].revents = 0;

  int ret;

  ret = poll (pfd, 2, -1);

  *rev1 = pfd[0].revents;

  *rev2 = pfd[1].revents;

  return ret;
}

// 'select'-related stuff

int gnulib_get_fd_setsize (void)
{
  return FD_SETSIZE;
}

static
int gnulib_select_wrapper (int nfds, void *rfds, void *wfds, void *efds,
                           double dtimeout, char **err_msg)
{
  // make a pointer already here for convenience (so it can be set to
  // NULL)
  struct timeval timeout;
  struct timeval *timeout_p = &timeout;

  if (dtimeout < 0)
    timeout_p = NULL;
  else
    {
      double ipart, fpart;
      fpart = modf (dtimeout, &ipart);
      timeout.tv_sec = lrint (ipart);
      timeout.tv_usec = lrint (fpart * 1000);
    }

  int ret = select (nfds,
                    (fd_set *) rfds,
                    (fd_set *) wfds,
                    (fd_set *) efds,
                    timeout_p);

  if (ret == -1)
    switch (errno)
      {
      case EBADF:
        *err_msg = "EBADF";
        break;
      case EINTR:
        *err_msg = "EINTR";
        break;
      case EINVAL:
        *err_msg = "EINVAL";
        break;
      default:
        *err_msg = "unknown error";
      }

  return ret;
}

inline static
fd_set *get_rfds (void)
{
  static fd_set rfds;

  return &rfds;
}

inline static
fd_set *get_wfds (void)
{
  static fd_set wfds;

  return &wfds;
}

inline static
fd_set *get_efds (void)
{
  static fd_set efds;

  return &efds;
}

void gnulib_fd_zero_all (void)
{
  FD_ZERO (get_rfds ());
  FD_ZERO (get_wfds ());
  FD_ZERO (get_efds ());
}

void gnulib_add_to_rfds (int fd)
{
  FD_SET (fd, get_rfds ());
}

void gnulib_add_to_wfds (int fd)
{
  FD_SET (fd, get_wfds ());
}

void gnulib_add_to_efds (int fd)
{
  FD_SET (fd, get_efds ());
}

int gnulib_select (int nfds, double dtimeout, char **err_msg)
{

  return gnulib_select_wrapper (nfds, get_rfds (), get_wfds (), get_efds (),
                                dtimeout, err_msg);
}

int gnulib_fd_isset_rfds (int fd)
{
  return FD_ISSET (fd, get_rfds ());
}

int gnulib_fd_isset_wfds (int fd)
{
  return FD_ISSET (fd, get_wfds ());
}

int gnulib_fd_isset_efds (int fd)
{
  return FD_ISSET (fd, get_efds ());
}

// end of 'select'-related stuff

int gnulib_fstat_reg_exec_nonsuid (int fd)
{
  struct stat statbuf;

  return (fstat (fd, &statbuf)
          || ! S_ISREG (statbuf.st_mode)
          || ! (statbuf.st_mode & S_IXUSR)
          || statbuf.st_mode & S_ISUID);
}

void gnulib_perror (const char *s)
{
  perror (s);
}

// 'sigset'-related stuff

void *gnulib_alloc_sigset (void)
{
  return gnulib_malloc (sizeof (sigset_t));
}

int gnulib_get_sigmask (void *dest)
{
  return pthread_sigmask (SIG_SETMASK, NULL, (sigset_t *)dest);
}

int gnulib_set_sigmask (void *src)
{
  return pthread_sigmask (SIG_SETMASK, (sigset_t *)src, NULL);
}

int gnulib_sigismember (const void *sig_set, int signum)
{
  return sigismember ((sigset_t *)sig_set, signum);
}

void gnulib_set_newset_one_signal (void *sig_set, int signum)
{
  sigemptyset ((sigset_t *)sig_set);

  sigaddset ((sigset_t *)sig_set, signum);
}

int gnulib_unblock (void *sig_set)
{
  return pthread_sigmask (SIG_UNBLOCK, (sigset_t *)sig_set, NULL);
}

// 'sigaction'-related stuff

void *gnulib_alloc_sigaction (void)
{
  return gnulib_malloc (sizeof (struct sigaction));
}

int gnulib_getsigaction (int signum, void *dest)
{
  return sigaction (signum, NULL, (struct sigaction *)dest);
}

int gnulib_setsigaction (int signum, void *src)
{
  return sigaction (signum, (struct sigaction *)src, NULL);
}

void gnulib_copy_sigaction (void *dest, const void *src)
{
  memcpy (dest, src, sizeof (struct sigaction));
}

void gnulib_remove_sa_restart (void *sig_action)
{
  ((struct sigaction *)sig_action)->sa_flags &= ~ SA_RESTART;
}

int gnulib_install_sighandler (int signum, void (*handler) (int))
{
  struct sigaction sa;

  sa.sa_handler = handler;

  sa.sa_flags = 0;

  if (sigemptyset (&sa.sa_mask)
      || sigaction (signum, &sa, NULL))
    return -1;
  else
    return 0;
}

// end of 'sigaction'-related stuff

// these are probably defined at Windows as macros in winsock.h or
// winsock2.h
uint32_t gnulib_htonl (uint32_t hostlong)
{
  return htonl (hostlong);
}
uint32_t gnulib_ntohl (uint32_t netlong)
{
  return ntohl (netlong);
}

int gnulib_mkdir_mode_0700 (const char *pathname)
{
  mkdir (pathname, 0700);
}

char* gnulib_getpass (const char *prompt)
{
  return getpass (prompt);
}
