/*
 * handle.c
 * This file is part of Network-inador
 *
 * Copyright (C) 2024 - Félix Arreola Rodríguez
 *
 * Network-inador 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 2 of the License, or
 * (at your option) any later version.
 *
 * Network-inador 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 Network-inador; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, 
 * Boston, MA  02110-1301  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <fcntl.h>
#include <unistd.h>

#include <assert.h>

#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>

#include "network-inador-private.h"
#include "netlink-events.h"

#include "interfaces.h"
#include "routes.h"
#include "resolv_manager.h"
#include "file_watcher.h"
#include "rtables.h"

struct _NetworkInadorWatchedPID {
	unsigned int source;
	pid_t child_pid;
	NetworkInadorChildWatchFunc func;
	void *data;
};

/* Variables si nosotros hacemos vigilancia de procesos */
static FList *network_inador_watched_childs = NULL;
static unsigned int network_inador_watched_child_source_counter = 1;
static int network_inador_pipes_child_notify[2] = {-1, -1};
static int network_inador_child_source_watcher = 0;

static unsigned int network_inador_add_watch_process (pid_t pid, NetworkInadorChildWatchFunc func, void *user_data) {
	struct _NetworkInadorWatchedPID *watcher;
	
	watcher = (struct _NetworkInadorWatchedPID *) malloc (sizeof (struct _NetworkInadorWatchedPID));
	assert (watcher != NULL);
	
	watcher->source = network_inador_watched_child_source_counter;
	network_inador_watched_child_source_counter++;
	watcher->child_pid = pid;
	watcher->func = func;
	watcher->data = user_data;
	
	network_inador_watched_childs = f_list_append (network_inador_watched_childs, watcher);
	
	return watcher->source;
}

static void network_inador_remove_watch_process (unsigned int source) {
	/* Recorrer la lista para eliminar el watch, ignorar si no está en la lista */
	struct _NetworkInadorWatchedPID *watcher;
	FList *g, *next;
	
	g = network_inador_watched_childs;
	while (g != NULL) {
		next = g->next;
		watcher = (struct _NetworkInadorWatchedPID *) g->data;
		
		if (watcher->source == source) {
			network_inador_watched_childs = f_list_delete_link (network_inador_watched_childs, g);
			free (watcher);
			
			break;
		}
		g = next;
	}
}

static void _network_inador_try_setup_route_events (NetworkInadorHandle *handle) {
	struct nl_sock * sock_req;
	sock_req = nl_socket_alloc ();
	int fd;
	
	if (nl_connect (sock_req, NETLINK_ROUTE) != 0) {
		nl_socket_free (sock_req);
		return;
	}
	
	nl_socket_set_nonblocking (sock_req);
	nl_socket_disable_seq_check (sock_req);
	
	fd = nl_socket_get_fd (sock_req);
	/* Set close-on-exec */
	fcntl (fd, F_SETFD, FD_CLOEXEC);
	
	handle->route_events.nl_sock = sock_req;
	handle->has_route_events = 1;
	handle->route_events.source = handle->ops.input_add (fd, POLLIN, netlink_events_route_events_handle_read, handle);
	
	nl_socket_add_memberships (sock_req, RTNLGRP_LINK, RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_IFINFO, RTNLGRP_IPV4_ROUTE, RTNLGRP_IPV6_ROUTE, 0);
	nl_socket_modify_cb (sock_req, NL_CB_VALID, NL_CB_CUSTOM, netlink_events_route_dispatcher, handle);
}

static void _network_inador_try_pipe_routes (NetworkInadorHandle *handle) {
	int ret, flags;
	int pipe_fds[2];
	
	ret = pipe (pipe_fds);
	if (ret < 0) {
		return;
	}
	
	handle->pipe_routes[0] = pipe_fds[0];
	handle->pipe_routes[1] = pipe_fds[1];
	
	flags = fcntl (pipe_fds[0], F_GETFL);
	fcntl (pipe_fds[0], F_SETFL, flags | O_NONBLOCK);
	handle->source_pipe_routes = handle->ops.input_add (pipe_fds[0], POLLIN, netlink_events_pipe_route_handle_read, handle);
}

static void network_inador_signal_handler_child (int signum) {
	//fprintf (stderr, "SIGCHLD session handler\n");
	char byte = 1;
	
	/* Avisar que un proceso hijo murió */
	if (network_inador_pipes_child_notify[1] >= 0) {
		write (network_inador_pipes_child_notify[1], &byte, 1);
	}
}

static void network_inador_child_pipe_read (void *data, int fd, int condition) {
	pid_t pid;
	int status;
	FList *g, *next;
	char byte;
	struct _NetworkInadorWatchedPID *watcher;
	int n;
	
	n = read (fd, &byte, 1);
		
	if (n <= 0) return;
	
	while ((pid = waitpid (-1, &status, WNOHANG)) > 0) {
		g = network_inador_watched_childs;
		fprintf (stderr, "El proceso hijo murió: %i\n", pid);
		while (g != NULL) {
			next = g->next;
			watcher = (struct _NetworkInadorWatchedPID *) g->data;
			
			if (watcher->child_pid == pid) {
				/* Este es la vigilancia del proceso que buscamos */
				watcher->func (watcher->data, pid, status);
				
				/* Como ya murió el proceso, lo eliminamos de la lista de vigilancia */
				network_inador_watched_childs = f_list_delete_link (network_inador_watched_childs, g);
				free (watcher);
				
				break;
			}
			
			g = next;
		}
	}
}

void network_inador_setup_child_handler (NetworkInadorHandle *handle) {
	struct sigaction act;
	sigset_t empty_mask;
	
	if (handle->ops.input_add == NULL) {
		/* No tenemos vigilancia de entrada, no podemos manejar las señales */
		return;
	}
	
	if (pipe (network_inador_pipes_child_notify) != 0) {
		return;
	}
	
	/* Queremos que estos pipes se cierren automáticamente */
	fcntl (network_inador_pipes_child_notify[0], F_SETFD, FD_CLOEXEC);
	fcntl (network_inador_pipes_child_notify[1], F_SETFD, FD_CLOEXEC);
	
	/* Establecer un manejador de SIGCHLD para los procesos hijos que se generen */
	sigemptyset (&empty_mask);
	act.sa_mask    = empty_mask;
	act.sa_flags   = 0;
	act.sa_handler = &network_inador_signal_handler_child;
	if (sigaction (SIGCHLD, &act, NULL) < 0) {
		perror ("Failed to register SIGCHLD handler");
		close (network_inador_pipes_child_notify[0]);
		network_inador_pipes_child_notify[0] = -1;
		close (network_inador_pipes_child_notify[1]);
		network_inador_pipes_child_notify[1] = -1;
		
		return;
	}
	
	network_inador_child_source_watcher = handle->ops.input_add (network_inador_pipes_child_notify[0], POLLIN, network_inador_child_pipe_read, handle);
	
	handle->ops.process_watch = network_inador_add_watch_process;
	handle->ops.process_unwatch = network_inador_remove_watch_process;
}

NetworkInadorHandle * network_inador_init_handle (struct NetworkInadorOps *network_inador_ops) {
	NetworkInadorHandle *handle;
	struct nl_sock * sock_req;
	
	handle = (NetworkInadorHandle *) malloc (sizeof (NetworkInadorHandle));
	assert (handle != NULL);
	
	memset (handle, 0, sizeof (NetworkInadorHandle));
	
	handle->pipe_routes[0] = -1;
	handle->pipe_routes[1] = -1;
	
	/* Copiar la información de eventos para vigilancia */
	handle->ops = *network_inador_ops;
	
	/* Crear el socket de peticiones */
	sock_req = nl_socket_alloc ();
	
	if (nl_connect (sock_req, NETLINK_ROUTE) != 0) {
		perror ("Falló conectar netlink socket\n");
		
		free (handle);
		return NULL;
	}
	
	handle->nl_sock_route = sock_req;
	
	/* Si tenemos una ops de configuración de eventos, entonces, configurar el otro socket de eventos async */
	if (handle->ops.input_add != NULL) {
		_network_inador_try_setup_route_events (handle);
		
		_network_inador_try_pipe_routes (handle);
	}
	
	network_inador_file_watcher_init (handle);
	
	/* Inicializar las interfaces (y las direcciones IP) */
	interfaces_init (handle);
	
	/* Inicializar las rutas */
	routes_init (handle);
	
	/* Inicializar los nombres de las tablas de ruteo */
	rtables_init (handle);
	
	/* Inicializar el resolv.conf */
	resolv_manager_init (handle);
	
	return handle;
}

void network_inador_destroy_handle (NetworkInadorHandle *handle) {
	if (network_inador_pipes_child_notify[0] >= 0) {
		if (handle->ops.input_remove != NULL) {
			handle->ops.input_remove (network_inador_child_source_watcher);
			network_inador_child_source_watcher = 0;
		}
		close (network_inador_pipes_child_notify[0]);
		network_inador_pipes_child_notify[0] = -1;
	}
	
	if (network_inador_pipes_child_notify[1] >= 0) {
		close (network_inador_pipes_child_notify[1]);
		network_inador_pipes_child_notify[1] = -1;
	}
	
	network_inador_file_watcher_cleanup (handle);
	
	if (handle->has_route_events) {
		/* Quitar la vigilancia */
		handle->ops.input_remove (handle->route_events.source);
		handle->route_events.source = 0;
		
		/* Cerrar el socket de eventos */
		nl_socket_free (handle->route_events.nl_sock);
		handle->route_events.nl_sock = NULL;
	}
	
	if (handle->source_pipe_routes != 0) {
		handle->ops.input_remove (handle->source_pipe_routes);
		handle->source_pipe_routes = 0;
	}
	
	if (handle->pipe_routes[0] >= 0) {
		close (handle->pipe_routes[0]);
		handle->pipe_routes[0] = -1;
	}
	
	if (handle->pipe_routes[1] >= 0) {
		close (handle->pipe_routes[1]);
		handle->pipe_routes[1] = -1;
	}
	
	/* TODO: Liberar las listas ligadas aquí */
	rtables_clean_up (handle);
	routes_clean_up (handle);
	interfaces_clean_up (handle);
	
	nl_socket_free (handle->nl_sock_route);
	handle->nl_sock_route = NULL;
	
	free (handle);
}