//network.c:

/*
 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2009-2014
 *
 *  This file is part of RoarAudio PlayList Daemon,
 *  a playlist management daemon for RoarAudio.
 *  See README for details.
 *
 *  This file is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 3
 *  as published by the Free Software Foundation.
 *
 *  RoarAudio 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 software; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301, USA.
 *
 */

#include "rpld.h"

#if defined(ROAR_HAVE_SETGID) && defined(ROAR_HAVE_SETUID) && defined(ROAR_HAVE_IO_POSIX)
#include <grp.h>
#include <pwd.h>
#endif

#define _AS(x) (sizeof((x))/sizeof(*(x)))

static struct {
 struct roar_vio_calls sock;
 int proto;
 const char * server;
 enum rpld_client_acclev acclev;
} g_listen[RPLD_MAX_LISTEN];

int  network_preinit(void) {
 size_t i;

 memset(g_listen, 0, sizeof(g_listen));

 for (i = 0; i < _AS(g_listen); i++)
  g_listen[i].proto = RPLD_PROTO_UNUSED;

 return 0;
}

int  network_add_server(const char * server, int port, int type, int proto, enum rpld_client_acclev acclev) {
 char unixpath_buf[128];
 size_t id;

 if ( acclev == ACCLEV_ERR )
  return -1;

 for (id = 0; id < _AS(g_listen); id++)
  if ( g_listen[id].proto == RPLD_PROTO_UNUSED )
   break;

 if ( id == _AS(g_listen) )
  return -1;

 if ( type == -1 ) {
  type = ROAR_SOCKET_TYPE_UNKNOWN;
 }

 if ( type == ROAR_SOCKET_TYPE_UNKNOWN && server == NULL && port == -1 ) {
#ifdef ROAR_HAVE_UNIX
  type = ROAR_SOCKET_TYPE_UNIX;
#elif defined(ROAR_HAVE_IPV4)
  type = ROAR_SOCKET_TYPE_TCP;
#elif defined(ROAR_HAVE_LIBDNET)
  type = ROAR_SOCKET_TYPE_DECNET;
#else
  ROAR_ERR("network_add_server(*): Can not find any working default address familiy.");
  return -1;
#endif
 }

 switch (type) {
  case ROAR_SOCKET_TYPE_UNIX:
    if ( server == NULL ) {
     if ( roar_env_render_path_r(unixpath_buf, sizeof(unixpath_buf), RPLD_SOCKNAME_UNIX) != -1 ) {
      server = unixpath_buf;
     } else {
      server = RPLD_SOCKNAME_UNIX_FALLBACK;
     }
    }
   break;
  case ROAR_SOCKET_TYPE_TCP:
    if ( server == NULL )
     server = RPLD_SOCKNAME_TCP;
    if ( port == -1 )
     port = RPLD_PORT;
  case ROAR_SOCKET_TYPE_DECNET:
    if ( server == NULL )
     server = RPLD_SOCKNAME_DECNET;
    if ( port == -1 )
     port = 0;
   break;
 }

 // TODO: FIXME: this code unlinks sockets that are aktive:
 if ( roar_vio_open_socket_listen(&(g_listen[id].sock), type, server, port) == -1 ) {
  if ( type == ROAR_SOCKET_TYPE_UNIX ) {
   unlink(server);
   if ( roar_vio_open_socket_listen(&(g_listen[id].sock), type, server, port) == -1 )
    return -1;
  }
 }

#if defined(ROAR_HAVE_SETGID) && defined(ROAR_HAVE_SETUID) && defined(ROAR_HAVE_IO_POSIX)
 if ( type == ROAR_SOCKET_TYPE_UNIX ) {
  if ( g_pwd != NULL || g_grp != NULL ) {
   if ( chown(server, g_pwd != NULL ? g_pwd->pw_uid : (uid_t)-1, g_grp != NULL ? g_grp->gr_gid : (gid_t)-1) == -1 ) {
    ROAR_WARN("Can not change ownership of socket: %s: %s", server, strerror(errno));
   }
  }
  if ( chmod(server, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1 ) {
   ROAR_WARN("Can not change permissions of socket: %s: %s", server, strerror(errno));
  }
 }
#endif

 g_listen[id].proto  = proto;
 g_listen[id].server = server;
 g_listen[id].acclev = acclev;

 return 0;
}

int  network_init(void) {
 return 0;
}

void network_uninit(void) {
 size_t id;

 for (id = 0; id < _AS(g_listen); id++) {
  if ( g_listen[id].proto != RPLD_PROTO_UNUSED )
   roar_vio_close(&(g_listen[id].sock));

  if ( g_listen[id].server != NULL )
   unlink(g_listen[id].server);
 }

 for (id = 0; id < RPLD_CLIENTS_MAX; id++) {
  client_delete(id);
 }
}

#define NUM_VIOS (RPLD_CLIENTS_MAX + RPLD_MAX_LISTEN)

void network_check(int block) {
 struct roar_vio_selecttv tv;
 struct roar_vio_select vios[NUM_VIOS];
 struct roar_vio_calls  * vio;
 int id;
 int i;
 int clientid;
 size_t num = 0;
 size_t min_client;

 ROAR_DBG("network_check(block=%i) = ?", block);

 tv.sec  = 0;

 switch (block) {
  case RPLD_YIELD_NONBLOCK:
    tv.nsec = 32000;
   break;
  case RPLD_YIELD_BLOCK:
    tv.nsec = 1024000;
   break;
  case RPLD_YIELD_LONGBLOCK:
    tv.sec  = 3600;
    tv.nsec = 0;
   break;
  default:
    tv.nsec = 1024000;
   break;
 }

 ROAR_DBG("num=%lu", (unsigned long int)num);

 for (i = 0; i < (int)_AS(g_listen); i++) {
  if ( g_listen[i].proto == RPLD_PROTO_UNUSED )
   continue;
  vios[num].ud.si = i;
  ROAR_VIO_SELECT_SETVIO(&(vios[num]), &(g_listen[i].sock), ROAR_VIO_SELECT_READ);
  num++;
 }

// ROAR_DBG("(int)(vios[0].vio->inst) = %i", (int)(vios[0].vio->inst));
 ROAR_DBG("num=%lu", (unsigned long int)num);

 min_client = num;

 for (id = 0; id < RPLD_CLIENTS_MAX; id++) {
  if ( (vio = client_get_vio(id)) != NULL ) {
   vios[num].ud.si = id;
   ROAR_VIO_SELECT_SETVIO(&(vios[num]), vio, client_get_eventsq(id));
   num++;
  }
 }

 ROAR_DBG("num=%lu", (unsigned long int)num);

 if ( num == 0 ) {
  rpld_exit();
  return;
 }

 if ( block != RPLD_YIELD_NONBLOCK )
  yield_watchdog_stop();

 if ( roar_vio_select(vios, num, &tv, NULL) < 1 ) {
  if ( block != RPLD_YIELD_NONBLOCK )
   yield_watchdog_start();
  ROAR_DBG("network_check(block=%i) = (void)", block);
  return;
 }

 if ( block != RPLD_YIELD_NONBLOCK )
  yield_watchdog_start();

 // first handle clients so new clients may not break the list
 for (i = min_client; i < NUM_VIOS && i < (int)num; i++) {
  if ( vios[i].eventsa & (ROAR_VIO_SELECT_READ|ROAR_VIO_SELECT_WRITE) ) {
   ROAR_DBG("network_check(block=%i): vios[i=%i].ud.si = %i", block, i, vios[i].ud.si);
   // handle client
   if ( client_handle(vios[i].ud.si, vios[i].eventsa) == -1 )
    client_delete(vios[i].ud.si);
  }
 }

 for (i = 0; i < (int)min_client && i < (int)num; i++) {
  if ( !(vios[i].eventsa & ROAR_VIO_SELECT_READ) )
   continue;
   // handle listen socket
  if ( (clientid = client_new(&vio)) == -1 ) {
   continue;
  }
  if ( roar_vio_accept(vio, &(g_listen[vios[i].ud.si].sock)) == -1 ) {
   roar_vio_clear_calls(vio);
   client_delete(clientid);
   continue;
  }

  if ( client_set_proto(clientid, g_listen[i].proto) == -1 ) {
   client_delete(clientid);
  }
  if ( client_set_acclev(clientid, g_listen[i].acclev, -1) == -1 ) {
   client_delete(clientid);
  }
 }

 ROAR_DBG("network_check(block=%i) = (void)", block);
}

//ll
