/*
 * signal.c - signal-management related operations implementation
 * 
 * Author  Ghassan Almaless (2008,2009,2010,2011,2012)
 *         Mohamed Lamine Karaoui (2015)
 *         Alain Greiner    (2016)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH 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; version 2.0 of the License.
 *
 * ALMOS-MKH 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 ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <hal_types.h>
#include <errno.h>
#include <thread.h>
#include <process.h>
#include <core.h>
#include <signal.h>

SIGNAL_HANDLER(kill_sigaction)
{
	struct thread_s *this;
  
	this = CURRENT_THREAD;
	this->state = S_KERNEL;

	printk(INFO, "INFO: Recieved signal %d, pid %d, tid %x, core %d  [ KILLED ]\n",
	       sig,
	       this->process->pid,
	       this,
	       cpu_get_id());

	sys_thread_exit((void*)EINTR);
}

//////////////////////////////////////////////////
void signal_manager_init( process_t * process )
{
	memset(&process->sig_mgr, 0, sizeof(process->sig_mgr));
	process->sig_mgr.sigactions[SIGCHLD] = SIG_IGNORE;
	process->sig_mgr.sigactions[SIGURG]  = SIG_IGNORE;
}

////////////////////////////////////////
error_t signal_init( thread_t * thread )
{
	thread->info.sig_state = 0;
	thread->info.sig_mask  = CURRENT_THREAD->info.sig_mask;
	return 0;
}

////////////////////////////////////////////////////
static error_t signal_rise_all( process_t * process, 
                                uint32_t    sig )
{
	thread_t     * thread;
	uint32_t       i;

	spinlock_lock( &process->th_lock );

	for( i = 0 ; i < process->th_nr ; i++ )
	{
		thread = process->th_tbl[i];
		hal_atomic_or( &thread->signals , (1 << sig) );
	}

	spinlock_unlock( &process->th_lock );

	sig_dmsg("\n[INFO] %s : %d threads have been signaled for process %d\n",
                        __FUNCTION__, process->th_nr , process->pid );

	return 0;
}

////////////////////////////////////////////////////
static error_t signal_rise_one( process_t * process,
                                uint32_t    sig )
{
	thread_t * thread;

	spinlock_lock( &process->th_lock );

//mdify to current_thread, not the one pointed by the sig_mgr ?
	if(process->sig_mgr.handler == NULL)
		thread = list_first(&process->th_root, struct thread_s, rope);
	else
		thread = process->sig_mgr.handler;

	spinlock_lock( &thread->lock );
	thread->info.sig_state |= (1 << sig);
	spinlock_unlock( &thread->lock );

	spinlock_unlock( &process->th_lock );

    sig_dmsg("\n[INFO] %s : Thread %u of process %u has received signal %u. sig_state = %x\n",        \
                        __FUNCTION__, thread_current_cpu(thread)->gid,                          \
                        process->pid, sig, thread->info.sig_state);

	return 0;
}

RPC_DECLARE( __signal_rise,                             \
                RPC_RET( RPC_RET_PTR(error_t, err)),    \
                RPC_ARG( RPC_ARG_VAL(pid_t,   pid),     \
                         RPC_ARG_VAL(uint32_t,  sig))     \
           )
{
         process_t   * process;
        struct hnode_s  *hnode;

        /* Avoid killing process0 and init */
        /* FIXME: Zero should not be hard-coded but obtains with something like MAIN_KERN */
        if( ((pid == PID_MIN_GLOBAL) || (pid == PID_MIN_GLOBAL+1))
                        && (current_cid == 0) )
        {
                *err = EPERM;
                sig_dmsg(1, "%s: can't kill process %u on cluster %u\n",           \
                                __FUNCTION__, PID_GET_LOCAL(pid), current_cid);
                goto SYS_RISE_ERR_PID;
        }

        /* Step 1 : lock the process manager */
        processs_manager_lock();

        /* Step 2 : Get the process' address */
        /* Case 1 : current cluster is the anchor and the owner. */
        if ( PID_GET_CLUSTER(pid) == current_cid )
        {
                sig_dmsg(1, "%s: process %u is in the processs manager array of cluster  %u\n",       \
                                __FUNCTION__, pid, current_cid);
                process = process_lookup(pid)->process;

        }
        else /* Case 2 : current cluster is not the anchor, so the struct
              * process_s is in its hash table.
              */
        {
                sig_dmsg(1, "%s: process %u is in the processs manager hash table of cluster  %u\n",  \
                                __FUNCTION__, pid, current_cid);
                hnode = hfind(processs_manager_get_htable(), (void*)pid);
                process    = ( process_t*) container_of(hnode,          \
                                 process_t, t_hnode);
        }

        /* Step 4 : check process' address */
        if ( process == NULL )
        {
                *err = ESRCH;
                goto SYS_RISE_ERR;
        }

        /* Step 5 : deliver signal */
        if((sig == SIGTERM) || (sig == SIGKILL))
                *err = signal_rise_all(process, sig);
        else
                *err = signal_rise_one(process, sig);

        /* Step 6 : unlock processs manager */
        processs_manager_unlock();

        return;

SYS_RISE_ERR:
        processs_manager_unlock();
SYS_RISE_ERR_PID:
        sig_dmsg(1, "%s: Cluster %u has not deliver signal %u to process %u (err %u)\n",  \
                        __FUNCTION__, current_cid, sig, err );

        return;
}

//////////////////////////////////
error_t signal_rise( pid_t    pid, 
                     cxy_t    location, 
                     uint32_t sig)
{
	error_t err;

        /* Check location error */
        if ( location == CID_NULL )
        {
                err = ESRCH;
                printk(WARNING, "%s: there is no process with pid %u\n", \
                                __FUNCTION__, pid);
                return err;
        }

        err = EAGAIN;
        RCPC( location, RPC_PRIO_SIG_RISE, __signal_rise,       \
                        RPC_RECV( RPC_RECV_OBJ(err) ),          \
                        RPC_SEND( RPC_SEND_OBJ(pid),            \
                                  RPC_SEND_OBJ(sig))            \
            );

	return err;
}

////////////////////////////////////
void signal_notify( thread_s * this)
{
	register uint32_t sig_state;
	register uint32_t sig;
	register struct sig_mgr_s *sig_mgr;
	uint32_t irq_state;

	sig_state = this->info.sig_state & this->info.sig_mask;
	sig       = 0;
 
	while((sig_state != 0) && ((sig_state & 0x1) == 0) && (sig < SIG_NR))
	{
		sig ++; 
		sig_state >>= 1;
	}
  
	if(sig)
	{
		cpu_disable_all_irq(&irq_state);

		if(thread_isSignaled(this))
		{
			cpu_restore_irq(irq_state);
			return;
		}

		thread_set_signaled(this);
		cpu_restore_irq(irq_state);

		spinlock_lock(&this->lock);
		this->info.sig_state &= ~(1 << sig);
		spinlock_unlock(&this->lock);

		sig_mgr = &this->process->sig_mgr;

		if(sig_mgr->sigactions[sig] == SIG_IGNORE)
			return;

		if(sig_mgr->sigactions[sig] == SIG_DEFAULT)
			kill_sigaction(sig);

		cpu_signal_notify(this, sig_mgr->sigactions[sig], sig);
	}
}
