/*
    clsync - file tree sync utility based on inotify

    Copyright (C) 2013  Dmitry Yu Okunev <dyokunev@ut.mephi.ru> 0x8E30679C

    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/>.
 */

#include "common.h"

#include <sys/un.h>	// for "struct sockaddr_un"
#include <sys/stat.h>	// mkdir()
#include <sys/types.h>	// mkdir()
#include <fcntl.h>	// mkdirat()
#include <glib.h>	// g_hash_table_foreach()


#include "indexes.h"
#include "main.h"
#include "ctx.h"
#include "error.h"
#include "sync.h"
#include "control.h"
#include "socket.h"

static pthread_t pthread_control;


static inline int control_error ( clsyncsock_t *clsyncsock_p, sockcmd_t *sockcmd_p, const char *const funct, const char *const args )
{
	debug ( 3, "%s(%s): %u: %s", funct, args, errno, strerror ( errno ) );
	return socket_reply ( clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_ECUSTOM, funct, args, errno, strerror ( errno ) );
}


int control_dump ( ctx_t *ctx_p, clsyncsock_t *clsyncsock_p, sockcmd_t *sockcmd_p )
{
	sockcmd_dat_dump_t *dat		= sockcmd_p->data;
	debug ( 3, "%s", dat->dir_path );
	return ( sync_dump ( ctx_p, dat->dir_path ) ) ?
	       control_error ( clsyncsock_p, sockcmd_p, "sync_dump", dat->dir_path ) :
	       socket_reply ( clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_DUMP );
}

int control_procclsyncsock ( socket_sockthreaddata_t *arg, sockcmd_t *sockcmd_p )
{
	int rc;
	clsyncsock_t	*clsyncsock_p =          arg->clsyncsock_p;
	ctx_t		*ctx_p        = ( ctx_t * ) arg->arg;

	switch ( sockcmd_p->cmd_id ) {
		case SOCKCMD_REQUEST_DUMP:
			rc = control_dump ( ctx_p, clsyncsock_p, sockcmd_p );
			break;

		case SOCKCMD_REQUEST_INFO:
			rc = socket_reply ( clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_INFO, ctx_p->config_block, ctx_p->label, ctx_p->flags, ctx_p->flags_set );
			break;

		case SOCKCMD_REQUEST_SET: {
				sockcmd_dat_set_t *dat = sockcmd_p->data;
				rc = ctx_set ( ctx_p, dat->key, dat->value );

				if ( rc ) {
					control_error ( clsyncsock_p, sockcmd_p, "ctx_set", dat->key );
					break;
				}

				rc = socket_reply ( clsyncsock_p, sockcmd_p, SOCKCMD_REPLY_SET );
				break;
			}

		case SOCKCMD_REQUEST_DIE:
			rc = sync_term ( SIGTERM );
			break;

		default:
			return EINVAL;
	}

	debug ( 3, "rc == %u", rc );
	return rc;
}

static inline void closecontrol ( ctx_t *ctx_p )
{
	if ( ctx_p->socket ) {
		close ( ctx_p->socket );
		ctx_p->socket = 0;
	}
}

int control_loop ( ctx_t *ctx_p )
{
	// Starting
	debug ( 1, "started (ctx_p->socket == %u)", ctx_p->socket );
	int s;

	while ( ( s = ctx_p->socket ) ) {
		// Check if the socket is still alive
		if ( socket_check_bysock ( s ) ) {
			error ( "Control socket closed [case 0]" );
			closecontrol ( ctx_p );
			continue;
		}

		// Waiting for event
		debug ( 3, "waiting for events on the socket" );
		fd_set rfds;
		FD_ZERO ( &rfds );
		FD_SET ( s, &rfds );
		int count = select ( s + 1, &rfds, NULL, NULL, NULL );
		// Processing the events
		debug ( 2, "got %i events with select()", count );

		// Processing the events: checks
		if ( count == 0 ) {
			debug ( 2, "select() timed out." );
			continue;
		}

		if ( count < 0 ) {
			debug ( 1, "Got negative events count. Closing the socket." );
			closecontrol ( ctx_p );
			continue;
		}

		if ( !FD_ISSET ( s, &rfds ) ) {
			error ( "Got event, but not on the control socket. Closing the socket (cannot use \"select()\")." );
			closecontrol ( ctx_p );
			continue;
		}

		// Processing the events: accepting new clsyncsock
		clsyncsock_t *clsyncsock_p = socket_accept ( s );

		if ( clsyncsock_p == NULL ) {
			if ( errno == EUSERS )	// Too many connections. Just ignoring the new one.
				continue;

			// Got unknown error. Closing control socket just in case.
			error ( "Cannot socket_accept()" );
			closecontrol ( ctx_p );
			continue;
		}

		debug ( 2, "Starting new thread for new connection." );
		socket_sockthreaddata_t *threaddata_p = socket_thread_attach ( clsyncsock_p );

		if ( threaddata_p == NULL ) {
			error ( "Cannot create a thread for connection" );
			closecontrol ( ctx_p );
			continue;
		}

		threaddata_p->procfunct		=  control_procclsyncsock;
		threaddata_p->clsyncsock_p	=  clsyncsock_p;
		threaddata_p->arg		=  ctx_p;
		threaddata_p->running		= &ctx_p->socket;
		threaddata_p->authtype		=  ctx_p->flags[SOCKETAUTH];
		threaddata_p->flags		=  0;

		if ( socket_thread_start ( threaddata_p ) ) {
			error ( "Cannot start a thread for connection" );
			closecontrol ( ctx_p );
			continue;
		}

#ifdef DEBUG
		// To prevent too often connections
		sleep ( 1 );
#endif
	}

	// Cleanup
	debug ( 1, "control_loop() finished" );
	return 0;
}

int control_run ( ctx_t *ctx_p )
{
	if ( ctx_p->socketpath != NULL ) {
		int ret =  0;
		int s   = -1;

		// initializing clsync-socket subsystem
		if ( ( ret = socket_init() ) )
			error ( "Cannot init clsync-sockets subsystem." );

		if ( !ret ) {
			clsyncsock_t *clsyncsock = socket_listen_unix ( ctx_p->socketpath );

			if ( clsyncsock == NULL ) {
				ret = errno;
			} else {
				s = clsyncsock->sock;
				socket_cleanup ( clsyncsock );
			}
		}

		// fixing privileges
		if ( !ret ) {
			if ( ctx_p->flags[SOCKETMOD] )
				if ( chmod ( ctx_p->socketpath, ctx_p->socketmod ) ) {
					error ( "Error, Cannot chmod(\"%s\", %o)",
					        ctx_p->socketpath, ctx_p->socketmod );
					ret = errno;
				}

			if ( ctx_p->flags[SOCKETOWN] )
				if ( chown ( ctx_p->socketpath, ctx_p->socketuid, ctx_p->socketgid ) ) {
					error ( "Error, Cannot chown(\"%s\", %u, %u)",
					        ctx_p->socketpath, ctx_p->socketuid, ctx_p->socketgid );
					ret = errno;
				}
		}

		// finish
		if ( ret ) {
			close ( s );
			return ret;
		}

		ctx_p->socket = s;
		debug ( 2, "ctx_p->socket = %u", ctx_p->socket );
		ret = pthread_create ( &pthread_control, NULL, ( void * ( * ) ( void * ) ) control_loop, ctx_p );
	}

	return 0;
}

int control_cleanup ( ctx_t *ctx_p )
{
	if ( ctx_p->socketpath != NULL ) {
		unlink ( ctx_p->socketpath );
		closecontrol ( ctx_p );
		// TODO: kill pthread_control and join
//		pthread_join(pthread_control, NULL);
		socket_deinit();
	}

	return 0;
}

