/* signal.c, liboop, copyright 1999 Dan Egnor

   This is free software; you can redistribute it and/or modify it under the
   terms of the GNU Lesser General Public License, version 2.1 or later.
   See the file COPYING for details. */

#include "oop.h"

#include <assert.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#define MAGIC 5131

struct sig_handler {
	struct sig_handler *next;
	oop_call_signal *f;
	void *v;
};

struct sig_signal {
	struct sig_handler *list,*ptr;
	struct sigaction old;
	volatile sig_atomic_t active;
};

struct oop_adapter_signal {
	oop_source oop;
	int magic,pipefd[2];
	oop_source *source; /* backing source */
	struct sig_signal sig[OOP_NUM_SIGNALS];
	int num_events;
};

static struct oop_adapter_signal *sig_owner[OOP_NUM_SIGNALS];

static oop_adapter_signal *verify_source(oop_source *source) {
	oop_adapter_signal * const s = (oop_adapter_signal *) source;
	assert(MAGIC == s->magic);
	return s;
}

static void do_pipe(struct oop_adapter_signal *s) {
	const char ch = '\0';
	while (write(s->pipefd[1],&ch,1) < 0 && EINTR == errno) ;
}

static void on_signal(int sig) {
	oop_adapter_signal * const s = sig_owner[sig];
	struct sigaction act;
	assert(NULL != s);

	/* Reset the handler, in case this is needed. */
	sigaction(sig,NULL,&act);
	act.sa_handler = on_signal;
	sigaction(sig,&act,NULL);

	assert(NULL != s->sig[sig].list);
	s->sig[sig].active = 1;
	do_pipe(s);
}

static void *on_pipe(oop_source *source,int fd,oop_event event,void *user) {
	oop_adapter_signal * const s = verify_source((oop_source *) user);
	int i;
	char buf[4096];

	assert(fd == s->pipefd[0]);
	assert(OOP_READ == event);
	while (read(s->pipefd[0],buf,sizeof buf) < 0 && EINTR == errno) ;

	for (i = 0; i < OOP_NUM_SIGNALS; ++i) {
		if (s->sig[i].active) {
			s->sig[i].active = 0;
			s->sig[i].ptr = s->sig[i].list;
		}
		if (NULL != s->sig[i].ptr) {
			struct sig_handler * const h = s->sig[i].ptr;
			s->sig[i].ptr = h->next;
			do_pipe(s); /* come back */
			return h->f(&s->oop,i,h->v);
		}
	}

	return OOP_CONTINUE;
}

static void sig_on_fd(oop_source *source,int fd,oop_event ev,
                      oop_call_fd *call,void *data) {
	oop_adapter_signal * const s = verify_source(source);
	s->source->on_fd(s->source,fd,ev,call,data);
}

static void sig_cancel_fd(oop_source *source,int fd,oop_event ev) {
	oop_adapter_signal * const s = verify_source(source);
	s->source->cancel_fd(s->source,fd,ev);
}

static void sig_on_time(oop_source *source,struct timeval when,
                        oop_call_time *call,void *data) {
	oop_adapter_signal * const s = verify_source(source);
	s->source->on_time(s->source,when,call,data);
}

static void sig_cancel_time(oop_source *source,struct timeval when,
                            oop_call_time *call,void *data) {
	oop_adapter_signal * const s = verify_source(source);
	s->source->cancel_time(s->source,when,call,data);
}

static void sig_on_signal(oop_source *source,int sig,
                          oop_call_signal *f,void *v) {
	oop_adapter_signal * const s = verify_source(source);
	struct sig_handler * const handler = oop_malloc(sizeof(*handler));
	if (NULL == handler) return; /* ugh */

	assert(sig > 0 && sig < OOP_NUM_SIGNALS && "invalid signal number");

	handler->f = f;
	handler->v = v;
	handler->next = s->sig[sig].list;
	s->sig[sig].list = handler;
	++s->num_events;

	if (NULL == handler->next) {
		struct sigaction act;

		assert(NULL == sig_owner[sig]);
		sig_owner[sig] = s;

		assert(0 == s->sig[sig].active);
		sigaction(sig,NULL,&act);
		s->sig[sig].old = act;
		act.sa_handler = on_signal;
#ifdef SA_NODEFER
		act.sa_flags &= ~SA_NODEFER;
#endif
		sigaction(sig,&act,NULL);
	}
}

static void sig_cancel_signal(oop_source *source,int sig,
                              oop_call_signal *f,void *v) {
	oop_adapter_signal * const s = verify_source(source);
	struct sig_handler **pp = &s->sig[sig].list;

	assert(sig > 0 && sig < OOP_NUM_SIGNALS && "invalid signal number");

	while (NULL != *pp && ((*pp)->f != f || (*pp)->v != v))
		pp = &(*pp)->next;

	if (NULL != *pp) {
		struct sig_handler * const p = *pp;

		if (NULL == p->next && &s->sig[sig].list == pp) {
			sigaction(sig,&s->sig[sig].old,NULL);
			s->sig[sig].active = 0;
			sig_owner[sig] = NULL;
		}

		*pp = p->next;
		if (s->sig[sig].ptr == p) s->sig[sig].ptr = *pp;
		--s->num_events;
		oop_free(p);
	}
}

static int fcntl_flag(int fd, int get_op, int set_op, int val) {
	const int flags = fcntl(fd,get_op,0);
	if (flags < 0) return -1;
	return fcntl(fd,set_op,flags|val);
}

oop_adapter_signal *oop_signal_new(oop_source *source) {
	int i;
	oop_adapter_signal * const s = oop_malloc(sizeof(*s));
	if (NULL == s) return NULL;
	assert(NULL != source);
	if (pipe(s->pipefd)
	||  fcntl_flag(s->pipefd[0],F_GETFD,F_SETFD,FD_CLOEXEC)
	||  fcntl_flag(s->pipefd[1],F_GETFD,F_SETFD,FD_CLOEXEC)
	||  fcntl_flag(s->pipefd[0],F_GETFL,F_SETFL,O_NONBLOCK)
	||  fcntl_flag(s->pipefd[1],F_GETFL,F_SETFL,O_NONBLOCK)) {
		oop_free(s);
		return NULL;
	}

	s->oop.on_fd = sig_on_fd;
	s->oop.cancel_fd = sig_cancel_fd;
	s->oop.on_time = sig_on_time;
	s->oop.cancel_time = sig_cancel_time;
	s->oop.on_signal = sig_on_signal;
	s->oop.cancel_signal = sig_cancel_signal;

	s->source = source;
	s->source->on_fd(s->source,s->pipefd[0],OOP_READ,on_pipe,s);
	s->num_events = 0;

	for (i = 0; i < OOP_NUM_SIGNALS; ++i) {
		s->sig[i].list = NULL;
		s->sig[i].ptr = NULL;
		s->sig[i].active = 0;
	}

	s->magic = MAGIC;
	return s;
}

void oop_signal_delete(oop_adapter_signal *s) {
	assert(0 == s->num_events && "cannot delete with signal handler");
	s->magic = 0;
	close(s->pipefd[0]);
	close(s->pipefd[1]);
	s->source->cancel_fd(s->source,s->pipefd[0],OOP_READ);
	oop_free(s);
}

oop_source *oop_signal_source(oop_adapter_signal *s) {
	return &s->oop;
}


syntax highlighted by Code2HTML, v. 0.9.1