#if ! defined(PROG_DISABLE_INOTIFY)

#include "declarations.h"

#if defined(HAVE_INOTIFY_INIT)
#include "barrier.h"

#include <fcntl.h>

#if defined(HAVE_SYS_INOTIFY_H)
#include <sys/inotify.h>
#endif

#include <sys/select.h>

#include "dsl_shr.h"
#include "dynload_bind_shr.h"
#include "worker_inotify.h"



static void worker_inotify(void *ta) {
	struct t_ctx *ti;

	ti = (struct t_ctx *) ta;

	/* This thread does not like to receive pthread_cancel() */
	ti->ar = THREAD_TERM_SELF;

	pthread_cleanup_push(worker_inotify_cancel, ti);

	if(worker_inotify_init(ti) == 0) {
		while(ti->pr == THREAD_STATE_RUN) {
			(void) worker_inotify_run((struct t_str *) ti->tp);
		}
	}

	/* Free allocated resources and terminate thread */
	pthread_cleanup_pop(1);

	(void) worker_inotify_finalize(ti);
}

static void worker_inotify_cancel(void *ta) {
	struct t_ctx *ti;

	ti = (struct t_ctx *) ta;

	if(ti->tp != NULL) {
		(void) worker_inotify_cancel_local(ti,
			(struct t_str *) ti->tp);

		(void) free(ti->tp);

		ti->tp = NULL;
	}

	ti->tr = THREAD_STATE_STOP;
}

static void worker_inotify_cancel_local(__UNUSED__ struct t_ctx *ti, struct t_str *tp) {
	unsigned int i;

	if(a_hnd != -1) {
		if(close(a_hnd) != 0) {
			LOGWARN(ERROR_NOERROR, SUBSYSTEM,
				_("Failed to close inotify descriptor"));
		}

		a_hnd = -1;
	}

	if(tp->event != NULL) {
		(void) free(tp->event);
	}

	if(tp->ta != NULL) {
		if(tp->ta->par_notify.s != NULL) {
			(void) free(tp->ta->par_notify.s);
		}

		(void) free(tp->ta);
	}

	for(i = 0; i < c_set; i++) {
		if(a_set[i] != NULL) {
			(void) free(a_set[i]->s);
			(void) free(a_set[i]);
		}
	}

	c_set = 0;
	a_set = NULL;
}

static void worker_inotify_finalize(struct t_ctx *ti) {
	(void) thread_exit(ti, NULL);
}

static int worker_inotify_init(struct t_ctx *ti) {
	int r;

	long *a;

	struct t_str *c;
	struct inotify_ctx *d, *p;

	/* Make a copy of callback stuff */
	if((p = malloc(sizeof(struct inotify_ctx))) == NULL) {
		LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
			_("Failed to allocate %zu bytes of memory"),
				sizeof(struct inotify_ctx));

		(void) free(ti->tp);

		return(-1);
	}

	d = (struct inotify_ctx *) ti->tp;

	p->par_notify.s = worker_inotify_init_op(d->par_notify.s);
	p->par_notify.n = d->par_notify.n;
	p->par_notify.d = d->par_notify.d;

	p->cb_notify = worker_inotify_cb_notify;

	/* Free inotify context allocated by dynload inotify init call... */
	(void) free(ti->tp);

	ti->tp = NULL;

	/* ...and allocate something new */
	if((c = malloc(sizeof(struct t_str))) == NULL) {
		LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
			_("Failed to allocate %zu bytes of memory"),
				sizeof(struct t_str));

		(void) free(p);

		return(-1);
	}

	c->ta = p;

	ti->tp = c;

	(void) thread_title(ti, APPLICATION_EXEC "_inotify");

	if((a = (long *) conf_fetch("cpu_inotify")) != NULL) {
		if(*a != 0) {
			(void) thread_affinity(*a);
		}
	}

	r = worker_inotify_init_local(ti, (struct t_str *) ti->tp);

	ti->tr = THREAD_STATE_IDLE;

	return(r);
}

static char *worker_inotify_init_op(char *s) {
	char *r;

	size_t t;

	if(s == NULL) {
		return(NULL);
	}

	if((t = str_len(s, STRING_ASCII)) == 0) {
		return(NULL);
	}

	APP_MALLOC_RET_VALUE(r, t + sizeof(char), NULL);

	(void) memcpy((void *) r, (const void *) s, t);

	r[t] = 0;

	return(r);
}

static int worker_inotify_init_local(__UNUSED__ struct t_ctx *ti, struct t_str *tp) {
	int f;

	if((tp->event = malloc(INOTIFY_BUFFER_SIZE)) == NULL) {
		LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
			_("Failed to allocate %zu bytes of memory"),
				(size_t) INOTIFY_BUFFER_SIZE);

		return(-1);
	}

	if((a_hnd = inotify_init()) == -1) {
		LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
			_("Failed to initialize inotify descriptor"));

		return(-1);
	}

	/* Set descriptor as non-blocking */
	if((f = fcntl(a_hnd, F_GETFL, 0)) == -1) {
		LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
			_("Failed to set inotify descriptor to non-blocking"
			  " mode"));

		return(-1);
	}

	if(fcntl(a_hnd, F_SETFL, f | O_NONBLOCK) == -1) {
		LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
			_("Failed to set inotify descriptor to non-blocking"
			  " mode"));

		return(-1);
	}

	return(0);
}

static void worker_inotify_run(struct t_str *tp) {
	int e;

	ssize_t t, u;

	struct inotify_event *n;

	fd_set fds;

	D_TIME_TV tv;

	/* Check if there is data available to read */
	tv.tv_sec = 0;
	tv.tv_usec = INOTIFY_DESC_TIMEOUT;

	FD_ZERO(&fds);
	FD_SET(a_hnd, &fds);

	if(select(a_hnd + 1, &fds, NULL, NULL, &tv) == 0) {
		/* No data was available to read */
		return;
	}

	/* Read what was available */
	u = 0;
	t = read(a_hnd, (void *) tp->event, INOTIFY_BUFFER_SIZE);

	switch(t) {
		case -1:
			(void) get_error(&e);

			if(e == EAGAIN) {
				/* No data was available to read */
				break;
			}

			LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
				_("Failed to read from inotify descriptor"));

			break;
		case 0:
			break;
		default:
			/* Get the notifications */
			while(u < t) {
				n = (struct inotify_event *) &tp->event[u];

				(void) worker_inotify_run_op(tp, n);

				u += sizeof(struct inotify_event) + n->len;
			}

			break;
	}
}

static void worker_inotify_run_op(struct t_str *tp, struct inotify_event *n) {
	struct a_not *a;

	if((a = worker_inotify_get_struct_by_handle((D_HANDLE) n->wd)) == NULL) {
		/* This handle is probably removed */
		return;
	}

	/* Run callback for this event if it is defined */
	(void) worker_inotify_run_at(tp, (D_HANDLE) n->wd,
		(unsigned int) n->mask,
		(unsigned int) n->cookie,
		(char *) a->s,
		(char *) n->name, (size_t) n->len);
}

static void worker_inotify_run_at(struct t_str *tp, D_HANDLE d, unsigned int e, unsigned int c, char *p, char *s, size_t t) {
	void (*cb)(struct d_par *);

	if(tp->ta->par_notify.s != NULL) {
		if(tp->ta->par_notify.s[0] == 0) {
			return;
		}

		cb = tp->ta->cb_notify;

		(void) worker_inotify_run_cb(&tp->ta->par_notify,
			d, e, c, p, s, t);

		(void) (*cb)(&tp->ta->par_notify);
	}
}

static void worker_inotify_run_cb(struct d_par *dp, D_HANDLE d, unsigned int e, unsigned int c, char *p, char *s, size_t t) {
	(void) dsl_cb_param_handle_add(dp,
		d, DSL_HANDLE_IS_INOTIFY, 0);

	(void) dsl_cb_param_uint_add(dp, e, 1);
	(void) dsl_cb_param_uint_add(dp, c, 2);

	(void) dsl_cb_param_string_add(dp,
		p, str_len(p, STRING_ASCII), 3);

	if(t == 0) {
		/* There is no name... */
		(void) dsl_cb_param_string_add(dp,
			"", 0, 4);
	}
	else {
		/* ...but there is */
		(void) dsl_cb_param_string_add(dp,
			s, str_len(s, STRING_ASCII), 4);
	}
}

static void worker_inotify_cb_notify(struct d_par *d) {
	(void) dsl_cb_inotify_notify(d);
}

static D_HANDLE worker_inotify_get_struct(char *s) {
	D_HANDLE d;

	unsigned int i;

	size_t u;

	struct a_not **t;

	/* Check if some slot can be reused */
	d = 0;

	D_MEM_BARRIER;

	(void) darkroom_reader_entry((void *) &t_sem_p);

	for(i = 0; i < c_set; i++) {
		if(a_set[i] == NULL) {
			d = (D_HANDLE) i + 1;

			break;
		}
	}

	(void) darkroom_reader_exit((void *) &t_sem_p);

	D_MEM_BARRIER;

	(void) darkroom_writer_entry((void *) &t_sem_p);

	if(d == 0) {
		/* No free slot found, allocate one */
		if((t = realloc(a_set,
			sizeof(struct a_not *) * (c_set + 1))) == NULL) {

			LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
				_("Failed to allocate %zu bytes of memory"),
					sizeof(struct a_not *) * (c_set + 1));

			D_MEM_BARRIER;

			(void) darkroom_writer_exit((void *) &t_sem_p);

			return(0);
		}

		a_set = t;

		if((a_set[c_set] = malloc(sizeof(struct a_not))) == NULL) {
			LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
				_("Failed to allocate %zu bytes of memory"),
					sizeof(struct a_not));

			D_MEM_BARRIER;

			(void) darkroom_writer_exit((void *) &t_sem_p);

			return(0);
		}

		(void) memset((void *) a_set[c_set], 0, sizeof(struct a_not));

		d = (D_HANDLE) ++c_set;
	}
	else {
		/* Reuse existing slot */
		if((a_set[d - 1] = malloc(sizeof(struct a_not))) == NULL) {
			LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
				_("Failed to allocate %zu bytes of memory"),
					sizeof(struct a_not));

			D_MEM_BARRIER;

			(void) darkroom_reader_exit((void *) &t_sem_p);

			return(0);
		}

		(void) memset((void *) a_set[d - 1], 0, sizeof(struct a_not));
	}

	/* Copy path name to better keep */
	u = str_len(s, STRING_ASCII);

	if((a_set[d - 1]->s = malloc(u + sizeof(char))) == NULL) {
		(void) free(a_set[d - 1]);

		a_set[d - 1] = NULL;

		D_MEM_BARRIER;

		(void) darkroom_reader_exit((void *) &t_sem_p);

		return(0);
	}

	(void) memcpy((void *) a_set[d - 1]->s, (const void *) s, u);

	a_set[d - 1]->s[u] = 0;

	(void) darkroom_writer_exit((void *) &t_sem_p);

	return(d);
}

static struct a_not *worker_inotify_get_struct_by_handle(D_HANDLE d) {
	struct a_not *r;

	if(d == 0) {
		return(NULL);
	}

	(void) darkroom_reader_entry((void *) &t_sem_p);

	D_MEM_BARRIER;

	if(a_set == NULL || d > (D_HANDLE) c_set) {
		r = NULL;
	}
	else {
		r = a_set[d - 1];
	}

	D_MEM_BARRIER;

	(void) darkroom_reader_exit((void *) &t_sem_p);

	return(r);
}

D_HANDLE inotifies_add_watch(char *s, int f) {
	int i, r;

	uint32_t n;

	struct a_not *a;

	D_HANDLE d;

	/* Keep these two in sync */
	int in_f[] = {
		INOTIFY_ACCESS, INOTIFY_MODIFY, INOTIFY_ATTRIB,
		INOTIFY_CLOSE_WRITE, INOTIFY_CLOSE_NOWRITE, INOTIFY_CLOSE,
		INOTIFY_OPEN,
		INOTIFY_MOVED_FROM, INOTIFY_MOVED_TO, INOTIFY_MOVE,
		INOTIFY_CREATE,
		INOTIFY_DELETE, INOTIFY_DELETE_SELF,
		INOTIFY_MOVE_SELF,
		-1
	};

	uint32_t in_n[] = {
		IN_ACCESS, IN_MODIFY, IN_ATTRIB,
		IN_CLOSE_WRITE, IN_CLOSE_NOWRITE, IN_CLOSE,
		IN_OPEN,
		IN_MOVED_FROM, IN_MOVED_TO, IN_MOVE,
		IN_CREATE,
		IN_DELETE, IN_DELETE_SELF,
		IN_MOVE_SELF
	};

	/* Make up watch event mask to watch... */
	n = 0;

	for(i = 0; ; i++) {
		if(in_f[i] == -1) {
			break;
		}

		if((f & in_f[i]) == in_f[i]) {
			n |= in_n[i];
		}
	}

	if(n == 0) {
		(void) flush_error();

		LOGWARN(ERROR_NOERROR, SUBSYSTEM,
			_("Failed to add inotify watch, at least one event"
			  " to watch is required"));

		return(0);
	}

	/* ...allocate slot for this watch... */
	if((d = worker_inotify_get_struct(s)) == 0) {
		return(0);
	}

	if((a = worker_inotify_get_struct_by_handle(d)) == NULL) {
		(void) free(a);

		a_set[d - 1] = NULL;

		return(0);
	}

	(void) darkroom_reader_entry((void *) &t_sem_p);

	D_MEM_BARRIER;

	/* ...and go for it */
	if((r = inotify_add_watch(a_hnd, (const char *) s, n)) == -1) {
		LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
			_("Failed to add inotify watch"));

		(void) free(a);

		a_set[d - 1] = NULL;

		D_MEM_BARRIER;

		(void) darkroom_reader_exit((void *) &t_sem_p);

		return(0);
	}

	a->d = r;

	D_MEM_BARRIER;

	(void) darkroom_reader_exit((void *) &t_sem_p);

	return(d);
}

int inotifies_del_watch(D_HANDLE h) {
	int r;

	struct a_not *a;

	if((a = worker_inotify_get_struct_by_handle(h)) == NULL) {
		return(-1);
	}

	if((r = inotify_rm_watch(a_hnd, a->d)) == -1) {
		LOGWARN(ERROR_SLIGHT, SUBSYSTEM,
			_("Failed to remove inotify watch"));
	}

	(void) free(a->s);
	(void) free(a);

	a_set[h - 1] = NULL;

	return(r);
}

void inotifies_init(struct inotify_ctx *a) {
	/* Wait until thread starts, as it copies callback stuff to itself */
	(void) thread_spawn(NULL, worker_inotify, (void *) a, NULL, NULL,
		THREAD_CLASS_INTERNAL, THREAD_WAIT);
}
#endif
#endif
