/*
    clsync - file tree sync utility based on inotify/kqueue
    
    Copyright (C) 2013-2014 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"			// ctx.h
#include "ctx.h"			// ctx_t
#include "error.h"			// debug()
#include "syscalls.h"			// read_inf()/write_inf()
#include "main.h"			// ncpus
#include "pthreadex.h"			// pthread_*_shared()
#include "malloc.h"			// xmalloc()

#ifdef CAPABILITIES_SUPPORT
# include <pthread.h>			// pthread_create()
# include <sys/inotify.h>		// inotify_init()
# include <sys/types.h>			// fts_open()
# include <sys/stat.h>			// fts_open()
# include <fts.h>			// fts_open()
# include <errno.h>			// errno
# include <sys/capability.h>		// capset()
# ifdef CGROUP_SUPPORT
#  include "cgroup.h"			// clsync_cgroup_deinit()
# endif
#endif

#include <unistd.h>			// execvp()
#include <glib.h>			// g_atomic_int_set()

#ifdef UNSHARE_SUPPORT
# include <sched.h>			// unshare()
#endif

#ifndef HL_LOCKS
# ifdef HL_LOCK_TRIES_AUTO
#  undef HL_LOCK_TRIES_AUTO
# endif
#endif

#ifdef HL_LOCK_TRIES_AUTO
# include <time.h>			// time()
# include <math.h>			// fabs()
#endif

#include "privileged.h"

#ifdef SECCOMP_SUPPORT
# include <syscall.h>			// __NR_*
# include <sys/prctl.h>			// prctl()
# include <linux/filter.h>		// struct sock_filter
# include <linux/seccomp.h>		// SECCOMP_RET_*

# ifndef __NR_shmdt
#  ifdef __i386__
#   warning [security] Caution! __NR_shmdt is not defined. Setting it to -222.
#   define __NR_shmdt -222
#  endif
# endif


# define syscall_nr (offsetof(struct seccomp_data, nr))

/* Read: http://www.rawether.net/support/bpfhelp.htm */
# define SECCOMP_COPY_SYSCALL_TO_ACCUM				\
	BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)

# define SECCOMP_ALLOW						\
	BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)

# define SECCOMP_DENY						\
	BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRAP)

# define SECCOMP_ALLOW_ACCUM_SYSCALL(syscall)			\
	BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##syscall, 0, 1),	\
	SECCOMP_ALLOW

# define FILTER_TABLE_NONPRIV						\
	SECCOMP_ALLOW_ACCUM_SYSCALL(futex),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(inotify_init1),			\
	SECCOMP_ALLOW_ACCUM_SYSCALL(alarm),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(open),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(write),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(close),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(wait4),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(unlink),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(tgkill),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(rt_sigreturn),			\
	SECCOMP_ALLOW_ACCUM_SYSCALL(brk),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(munmap),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(wait4),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(rmdir),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(exit_group),			\
	SECCOMP_ALLOW_ACCUM_SYSCALL(select),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(read),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(rt_sigprocmask),			\
	SECCOMP_ALLOW_ACCUM_SYSCALL(rt_sigaction),			\
	SECCOMP_ALLOW_ACCUM_SYSCALL(nanosleep),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(shmdt),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(clone),		/* for --threading */		\
	SECCOMP_ALLOW_ACCUM_SYSCALL(set_robust_list),	/* for --threading? */		\
	SECCOMP_ALLOW_ACCUM_SYSCALL(madvise),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(exit),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(clock_gettime),			\

# ifdef __i386__
#  define FILTER_TABLE_ARCHDEPENDED			/* unused */	\
	SECCOMP_ALLOW_ACCUM_SYSCALL(fstat64),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(lstat64),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(stat64),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(time),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(mmap2),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(gettimeofday),			\
	SECCOMP_ALLOW_ACCUM_SYSCALL(_newselect),			\

# else
#  define FILTER_TABLE_ARCHDEPENDED					\
	SECCOMP_ALLOW_ACCUM_SYSCALL(fstat),		/* unused */	\
	SECCOMP_ALLOW_ACCUM_SYSCALL(lstat),				\
	SECCOMP_ALLOW_ACCUM_SYSCALL(stat),		/* unused */	\
	SECCOMP_ALLOW_ACCUM_SYSCALL(mmap),				\

# endif


/* Syscalls allowed to non-privileged thread */
static struct sock_filter filter_table[] = {
	SECCOMP_COPY_SYSCALL_TO_ACCUM,
	FILTER_TABLE_NONPRIV
	FILTER_TABLE_ARCHDEPENDED
	SECCOMP_DENY,
};
static struct sock_filter filter_w_mprotect_table[] = {
	SECCOMP_COPY_SYSCALL_TO_ACCUM,
	FILTER_TABLE_NONPRIV
	FILTER_TABLE_ARCHDEPENDED
	SECCOMP_ALLOW_ACCUM_SYSCALL(mprotect),
	SECCOMP_DENY,
};

int nonprivileged_seccomp_init(ctx_t *ctx_p) {
	struct sock_fprog *filter_p;
	struct sock_fprog filter = {
		.len = (unsigned short)(sizeof(filter_table)/sizeof(filter_table[0])),
		.filter = filter_table,
	};
	struct sock_fprog filter_w_mprotect = {
		.len = (unsigned short)(sizeof(filter_w_mprotect_table)/sizeof(filter_w_mprotect_table[0])),
		.filter = filter_w_mprotect_table,
	};

	debug(5, "enabling the seccomp");

	filter_p = (ctx_p->flags[PERMIT_MPROTECT] ? &filter_w_mprotect : &filter);

	SAFE (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0),			return -1);
	SAFE (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filter_p),	return -1);

	return 0;
}
#endif

int (*privileged_fork_execvp)(const char *file, char *const argv[]);
int (*privileged_kill_child)(pid_t pid, int sig, char ignoreerrors);

#ifdef CAPABILITIES_SUPPORT
pid_t		helper_pid = 0;
pthread_t	privileged_thread;
pthread_mutex_t	*pthread_mutex_privileged_p;
pthread_mutex_t	*pthread_mutex_action_signal_p;
pthread_mutex_t	*pthread_mutex_action_entrance_p;
pthread_mutex_t	*pthread_mutex_runner_p;
pthread_cond_t	*pthread_cond_privileged_p;
pthread_cond_t	*pthread_cond_action_p;
pthread_cond_t	*pthread_cond_runner_p;

# ifdef READWRITE_SIGNALLING
int priv_read_fd;
int priv_write_fd;
int nonp_read_fd;
int nonp_write_fd;
# endif

enum privileged_action {
	PA_UNKNOWN = 0,

	PA_SETUP,

	PA_DIE,

	PA_LSTAT64,

	PA_FTS_OPEN,
	PA_FTS_READ,
	PA_FTS_CLOSE,

	PA_INOTIFY_INIT,
	PA_INOTIFY_INIT1,
	PA_INOTIFY_ADD_WATCH,
	PA_INOTIFY_RM_WATCH,

	PA_FORK_EXECVP,

	PA_KILL_CHILD,

	PA_CLSYNC_CGROUP_DEINIT,

	PA_WAITPID,
};

struct pa_lstat64_arg {
	char		 path_buf[PATH_MAX];
	const char	*path;
};

struct pa_fts_open_arg {
	char		_path_argv[MAXARGUMENTS+1][PATH_MAX];
	char		*path_argv[MAXARGUMENTS+1];
	char *const	*path_argv_p;
	int options;
	int (*compar)(const FTSENT **, const FTSENT **);
};

struct pa_inotify_add_watch_arg {
	int fd;
	char		 pathname[PATH_MAX];
	const char	*pathname_p;
	uint32_t mask;
};

struct pa_inotify_rm_watch_arg {
	int fd;
	int wd;
};

struct pa_fork_execvp_arg {
	char		 file[PATH_MAX];
	const char	*file_p;
	char		_argv[MAXARGUMENTS+1][BUFSIZ];
	char		*argv[MAXARGUMENTS+1];
	char *const	*argv_p;
};

struct pa_kill_child_arg {
	pid_t pid;
	int   signal;
	char  ignoreerrors;
};

struct pa_waitpid_arg {
	pid_t  pid;
	int    status;
	int    options;
};

struct pa_arg {
	struct pa_lstat64_arg		 lstat64;
	struct pa_fts_open_arg		 fts_open;
	struct pa_inotify_add_watch_arg	 inotify_add_watch;
	struct pa_inotify_rm_watch_arg	 inotify_rm_watch;
	struct pa_fork_execvp_arg	 fork_execvp;
	struct pa_kill_child_arg	 kill_child;
	struct pa_waitpid_arg		 waitpid;
	void				*void_v;
	ctx_t				*ctx_p;
	uint32_t			 uint32_v;
};

# ifdef HL_LOCKS
enum highload_lock_id {
	HLLOCK_HANDLER = 0,

	HLLOCK_MAX
};
typedef enum highlock_lock_id hllockid_t;

enum highlock_lock_state {
	HLLS_UNREADY	= 0x00,
	HLLS_READY	= 0x01,
	HLLS_FALLBACK	= 0x02,
	HLLS_SIGNAL	= 0x04,
	HLLS_GOTSIGNAL	= 0x08,
	HLLS_WORKING	= 0x10,
};
typedef enum highlock_lock_state hllock_state_t;

struct hl_lock {
	volatile int			locallock_hl_setstate_ifstate;
	volatile int			enabled;
	volatile int			count_wait[HLLOCK_MAX];
	volatile int			count_signal[HLLOCK_MAX];
	volatile hllock_state_t		state[HLLOCK_MAX];
#  ifdef HL_LOCK_TRIES_AUTO
	volatile unsigned long		tries[PC_MAX];
	volatile unsigned long		count[PC_MAX];
	volatile unsigned long		delay[PC_MAX];
	volatile double			tries_step[PC_MAX];

#   define				tries_cur tries[callid]
#  else
	volatile unsigned long		tries;
#   define				tries_cur tries
#  endif
};
# endif

struct pa_fts_read_ret {
	FTSENT		ftsent;
	char		fts_accpath[PATH_MAX];
	char		fts_path[PATH_MAX];
	char		fts_name[PATH_MAX];
};
struct pa_ret {
	stat64_t		stat64;
	struct pa_fts_read_ret	fts_read;
};
struct cmd {
	volatile struct pa_arg		 arg;
	volatile enum privileged_action	 action;
# ifdef HL_LOCKS
	volatile unsigned long		 hl_lock_tries;
# endif
};
struct cmd_ret {
	volatile struct pa_ret		 ret_buf;
	volatile void			*ret;
	volatile int			 _errno;
	volatile int			 processing_longcmd;
};
volatile struct cmd		*cmd_p;
volatile struct cmd_ret		*cmd_ret_p;
# ifdef HL_LOCKS
volatile struct hl_lock		*hl_lock_p;
# endif

# ifdef HL_LOCKS
static inline void hl_lock_init(volatile struct hl_lock *hl_lock_p) {
	debug(10, "");
	hl_lock_p->enabled = 1;
	hl_lock_p->locallock_hl_setstate_ifstate = 1;
#  ifdef HL_LOCK_TRIES_AUTO
	int i;
	i = 0;
	while (i < PC_MAX) {
		hl_lock_p->tries[i]		= HL_LOCK_TRIES_INITIAL;
		hl_lock_p->delay[i]		= ((unsigned long)~0)>>2;
		hl_lock_p->tries_step[i]	= HL_LOCK_AUTO_K;
		i++;
	}
#  else
	hl_lock_p->tries	= HL_LOCK_TRIES_INITIAL;
#  endif
	return;
}
# endif

struct pa_options {
	synchandler_args_t args[SHARGS_MAX];
	char *label;
	char *exithookfile;
	char *preexithookfile;
	char *permitted_hookfile[MAXPERMITTEDHOOKFILES+1];
	int   permitted_hookfiles;
	int   isprocsplitting;
	int   shm_mprotect;
};

int (*_privileged_lstat64)		(
		const char *path, stat64_t *buf
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	);

FTS *(*_privileged_fts_open)		(
		char * const *path_argv,
		int options,
		int (*compar)(const FTSENT **, const FTSENT **)
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	);

FTSENT *(*_privileged_fts_read)		(
		FTS *ftsp
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	);

int (*_privileged_fts_close)		(
		FTS *ftsp
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	);

int (*_privileged_inotify_init)		();
int (*_privileged_inotify_init1)	(int flags);

int (*_privileged_inotify_add_watch)	(
		int fd,
		const char *pathname,
		uint32_t mask
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	);

int (*_privileged_inotify_rm_watch)	(
		int fd,
		int wd
	);

int (*_privileged_clsync_cgroup_deinit)	(ctx_t *ctx_p);

pid_t (*_privileged_waitpid)		(pid_t pid, int *status, int options);

int cap_enable(__u32 caps) {
	debug(1, "Enabling Linux capabilities 0x%x", caps);
	struct __user_cap_header_struct	cap_hdr = {0};
	struct __user_cap_data_struct	cap_dat = {0};

	cap_hdr.version = _LINUX_CAPABILITY_VERSION;
	if (capget(&cap_hdr, &cap_dat) < 0) {
		error("Cannot get capabilities with capget()");
		return errno;
	}

	debug(3, "old: cap.eff == 0x%04x; new: cap.eff == 0x%04x", cap_dat.effective, cap_dat.effective|caps);
	cap_dat.effective |= caps;

	if (capset(&cap_hdr, &cap_dat) < 0) {
		error("Cannot set capabilities with capset().");
		return errno;
	}

	return 0;
}

int cap_drop(ctx_t *ctx_p, __u32 caps) {
	debug(1, "Dropping all Linux capabilities but 0x%x", caps);

	struct __user_cap_header_struct	cap_hdr = {0};
	struct __user_cap_data_struct	cap_dat = {0};

	cap_hdr.version = _LINUX_CAPABILITY_VERSION;
	if (capget(&cap_hdr, &cap_dat) < 0) {
		error_or_debug((ctx_p->flags[CAP_PRESERVE] != CAP_PRESERVE_TRY) ? -1 : 3, "Cannot get capabilities with capget()");
		return errno;
	}
	debug(3, "old: cap.eff == 0x%04x; cap.prm == 0x%04x; cap.inh == 0x%04x.",
		cap_dat.effective, cap_dat.permitted, cap_dat.inheritable);

	switch (ctx_p->flags[CAPS_INHERIT]) {
		case CI_PERMITTED:
			cap_dat.inheritable = cap_dat.permitted;
			break;
		case CI_DONTTOUCH:
			break;
		case CI_CLSYNC:
			cap_dat.inheritable = caps;
			break;
		case CI_EMPTY:
			cap_dat.inheritable = 0;
			break;
	}
	cap_dat.effective  = caps;
	cap_dat.permitted  = caps;

	debug(3, "new: cap.eff == 0x%04x; cap.prm == 0x%04x; cap.inh == 0x%04x.",
		cap_dat.effective, cap_dat.permitted, cap_dat.inheritable);

	if (capset(&cap_hdr, &cap_dat) < 0) {
		error_or_debug((ctx_p->flags[CAP_PRESERVE] != CAP_PRESERVE_TRY) ? -1 : 3, "Cannot set capabilities with capset().");
		return errno;
	}

	return 0;
}

#endif
int __privileged_kill_child_itself(pid_t child_pid, int signal, char ignoreerrors) {
	// Checking if it's a child
	if (waitpid(child_pid, NULL, WNOHANG)>=0) {
		debug(3, "Sending signal %u to child process with pid %u.",
			signal, child_pid);
		if (kill(child_pid, signal)) {
			if (!ignoreerrors)
				error("Got error while kill(%u, %u)", child_pid, signal);
			return errno;
		}

		waitpid_timed(child_pid, NULL, SLEEP_SECONDS, 0);
	} else
		return ENOENT;

	return 0;
}
#ifdef CAPABILITIES_SUPPORT

int pa_strcmp(const char *s1, const char *s2, int isexpanded) {
	if (isexpanded)
		return strcmp(s1, s2);

	{
		const char *s1_start = NULL;
		const char *s2_start = NULL;
		while (1) {
			while (1) {
				if (!*s1 || !*s2) {
					if (!*s1 && s1_start != NULL)
						return 0;
					return *s1 != *s2;
				}

				if (*s1 == '%') {
					s1++;
					while (*s1 && *s1 != '%') s1++;
					s1++;
					s1_start = s1;
					s2_start = s2;
					continue;
				}

				if (*s1 != *s2)
					break;

				s1++;
				s2++;
			}

			if (s2_start == NULL)
				break;

			s2_start++;
			s1 = s1_start;
			s2 = s2_start;
		}

		return *s1 != *s2;
	}
}

int privileged_execvp_check_arguments(struct pa_options *opts, const char *u_file, char *const *u_argv) {
	int a_i;
	size_t u_argc;
	synchandler_args_t *args = opts->args;
	
	debug(9, "");

	// Counting the number of arguments
	u_argc = 0;
	while (u_argv[u_argc] != NULL) u_argc++;

	a_i = 0;
	do {
		int i;
		int    argc;
		char **argv;
		char  *isexpanded;

		argc       = args[a_i].c;
		argv       = args[a_i].v;
		isexpanded = args[a_i].isexpanded;

		debug(8, "Checking the number of arguments: %i <> %i", argc, u_argc);
		if (argc != u_argc)
			continue;

		critical_on (!argc);

		debug(8, "Checking the execution file: \"%s\" <> \"%s\"; isexpanded == %i", argv[0], u_file, isexpanded[0]);
		if (pa_strcmp(argv[0], u_file, isexpanded[0])) {
			debug(1, "The file to be executed didn't match (argv[0] != u_file): \"%s\" != \"%s\"", argv[0], u_file);
			break;
		}

		debug(8, "Checking arguments");
		i = 1;
		while (i < argc) {
			if (pa_strcmp(argv[i], u_argv[i], isexpanded[i])) {
				debug(1, "An argument #%i didn't match (argv[%i] != u_argv[%i]): \"%s\" != \"%s\"", argv[i], argv[i]);
				break;
			}
			i++;
		}

		// All arguments right?
		if (i == argc)
			break;

		// No? Ok the next "shargs".
	} while (++a_i < SHARGS_MAX);

	if (a_i < SHARGS_MAX)
		return 0;

	if (u_argc == 2) {
		int i;

		if ((opts->exithookfile != NULL) || (opts->preexithookfile != NULL)) {
			if (!strcmp(opts->label, u_argv[1])) {
				if (opts->exithookfile != NULL)
					if (!strcmp(opts->exithookfile,    u_file))
						return 0;
				if (opts->preexithookfile != NULL)
					if (!strcmp(opts->preexithookfile, u_file))
						return 0;
			}
		}

		i = 0;
		while (i < opts->permitted_hookfiles) {
			if (!strcmp(opts->permitted_hookfile[i], u_file))
				return 0;
			i++;
		}
	}

	debug(1, "a_i == %i; SHARGS_MAX == %i; u_argc == %i", SHARGS_MAX, a_i, u_argc);
	critical("Arguments are wrong. This should happend only on hacking attack.");
	return EPERM;
}

int pa_setup(struct pa_options *opts, ctx_t *ctx_p, uid_t *exec_uid_p, gid_t *exec_gid_p) {
	synchandler_args_t *args = opts->args;
	int a_i;

	a_i = 0;
	do {
		int i, argc_s;
		char **argv_s, **argv_d, *isex_s, *isex_d;

		argc_s = ctx_p->synchandler_args[a_i].c;
		argv_s = ctx_p->synchandler_args[a_i].v;
		isex_s = ctx_p->synchandler_args[a_i].isexpanded;
		argv_d = args[a_i].v;
		isex_d = args[a_i].isexpanded;

		if (argc_s >= MAXARGUMENTS)
			critical("Too many arguments");

		if (argc_s < 1)
			continue;

		argv_d[0] = strdup_protect(ctx_p->handlerfpath, PROT_READ);

		i = 0;
		while (i < argc_s) {
			argv_d[i+1] = strdup_protect(argv_s[i], PROT_READ);
			isex_d[i+1] = isex_s[i];
			i++;
		}
		i++;
		argv_d[i] = NULL;
		args[a_i].c = i;

		a_i++;
	} while (++a_i < SHARGS_MAX);

	*exec_uid_p = ctx_p->synchandler_uid;
	*exec_gid_p = ctx_p->synchandler_gid;

	opts->label = strdup_protect(ctx_p->label, PROT_READ);
	if (ctx_p->exithookfile != NULL)
		opts->exithookfile = strdup_protect(ctx_p->exithookfile, PROT_READ);
	if (ctx_p->preexithookfile != NULL)
		opts->preexithookfile = strdup_protect(ctx_p->preexithookfile, PROT_READ);

	{
		int i = 0;

		while (i < ctx_p->permitted_hookfiles) {
			opts->permitted_hookfile[i] = strdup_protect(ctx_p->permitted_hookfile[i], PROT_READ);
			i++;
		}
		opts->permitted_hookfile[i] = NULL;
		opts->permitted_hookfiles   = i;
	}

	return 0;
}

int pa_unsetup(struct pa_options *opts) {
#ifdef TODO_FIX
	// segfaults: gdb --args clsync -K lxc-brother-atomic-sync -l jabber --pre-exit-hook wlxc-stop --chroot= --pivot-root=off -d9 -b0 -Ystderr

	free(opts->exithookfile);
	free(opts->preexithookfile);
	free(opts->label);

	{
		int a_i = 0;
		do {
			int i;

			i = 0;
			while (i < opts->args[a_i].c) {
				free(opts->args[a_i].v[i]);
				i++;
			}
		} while(++a_i < SHARGS_MAX);
	}

	{
		int i = 0;
		while (i < opts->permitted_hookfiles) {
			free(opts->permitted_hookfile[i]);
			i++;
		}
	}
#endif

	return 0;
}

static int helper_isalive_cache;
static inline int helper_isalive_proc() {
	int rc;
	debug(12, "helper_pid == %u", helper_pid);

	if ((rc=waitpid(helper_pid, NULL, WNOHANG))>=0)
		return helper_isalive_cache=1;

	debug(1, "waitpid(%u, NULL, WNOHANG) => %i", helper_pid, rc);

	return helper_isalive_cache=0;
}
static inline int helper_isalive_thread() {
	int rc;
	debug(12, "");

	if ((rc=pthread_kill(privileged_thread, 0)))
		return helper_isalive_cache=0;

	debug(1, "pthread_kill(privileged_thread, 0) => %i", helper_pid, rc);

	return helper_isalive_cache=1;
}
static inline int helper_isalive() {
	return helper_pid ? helper_isalive_proc() : helper_isalive_thread();
}

int privileged_check() {
	if (helper_pid)
		critical_on(!helper_isalive_proc());
	return 0;
}

# ifdef HL_LOCKS

static inline int hl_isanswered(int lockid) {
	return hl_lock_p->count_wait[lockid] == hl_lock_p->count_signal[lockid]+1;
}

static inline int hl_isready(int lockid) {
	return hl_lock_p->count_wait[lockid] == hl_lock_p->count_signal[lockid];
}

static inline void hl_setstate(int lockid, hllock_state_t stateid) {
	g_atomic_int_set(&hl_lock_p->state[lockid], stateid);
}

int hl_setstate_ifstate(int lockid, hllock_state_t stateid_new, hllock_state_t stateid_old_mask) {
	static long long counter = 0;
	volatile int *local_lock_p = &hl_lock_p->locallock_hl_setstate_ifstate;
	debug(90, "%i, 0x%o, 0x%o", lockid, stateid_new, stateid_old_mask);

	if (helper_pid || parent_pid) {
		if (((++counter) & 0xffffff) == 0) {
			if (helper_pid) {
				critical_on(!helper_isalive_proc());
			} else {
				critical_on(!parent_isalive());
			}
		}
	}

	if (!g_atomic_int_dec_and_test(local_lock_p)) {
		g_atomic_int_inc(local_lock_p);
		return 0;
	}

	if (!(hl_lock_p->state[lockid]&stateid_old_mask)) {
		g_atomic_int_inc(local_lock_p);
		return 0;
	}

	debug(50, "success");
	g_atomic_int_set(&hl_lock_p->state[lockid], stateid_new);
	g_atomic_int_inc(local_lock_p);
	return 1;
}

static inline int hl_wait(
		int lockid
#  ifdef HL_LOCK_TRIES_AUTO
		, unsigned long hl_lock_tries
#  endif
) {
	volatile long try = 0;

	debug(15, "");

	while (hl_lock_p->state[lockid] == HLLS_GOTSIGNAL);
	while (!hl_isready(lockid));
	hl_setstate(lockid, HLLS_READY);
	hl_lock_p->count_wait[lockid]++;

	while (try++ < hl_lock_tries)
		if (hl_lock_p->state[lockid] == HLLS_SIGNAL) {
			hl_setstate(lockid, HLLS_GOTSIGNAL);
			debug(15, "got signal");
			return 1;
		}

	while (!hl_setstate_ifstate(lockid, HLLS_FALLBACK, HLLS_READY|HLLS_SIGNAL));
	debug(14, "fallback: hl_lock_p->count_wait[%u] == %u; hl_lock_p->count_signal[%u] = %u", lockid, hl_lock_p->count_wait[lockid], lockid, hl_lock_p->count_signal[lockid]);
	return 0;
}

static inline int hl_signal(int lockid) {
	debug(15, "%u", lockid);
	hl_lock_p->count_signal[lockid]++;

	if (hl_setstate_ifstate(lockid, HLLS_SIGNAL, HLLS_READY)) {
		while (hl_lock_p->state[lockid] != HLLS_GOTSIGNAL) {
			if (hl_lock_p->state[lockid] == HLLS_FALLBACK) {
				debug(15, "fallback");
				return 0;
			}
			debug(95, "state == %i != %i, %i", hl_lock_p->state[lockid], HLLS_GOTSIGNAL, HLLS_FALLBACK);
		}
		debug(15, "the signal is sent");
		hl_setstate(lockid, HLLS_WORKING);
		return 1;
	}

	debug(15, "not ready");
	return 0;
}

void hl_shutdown(int lockid) {
	debug(1, "");

#  ifdef PARANOID
	critical_on (HLLOCK_MAX != 1);	// TODO: do this on compile time (not on running time)
#   ifdef HL_LOCK_TRIES_AUTO
	memset((void *)hl_lock_p->tries, 0, sizeof(hl_lock_p->tries));
#   else
	hl_lock_p->tries = 0;
#   endif
#  endif
	hl_lock_p->state[lockid]	 = HLLS_FALLBACK;
	hl_lock_p->enabled 	 = 0;

	return;
}

# endif

int privileged_handler(ctx_t *ctx_p)
{
# ifdef READWRITE_SIGNALLING
	char buf[1] = {0};
# else
	struct timespec wait_timeout = {0};
# endif

	int setup = 0;
	uid_t exec_uid = 65535;
	gid_t exec_gid = 65535;
	
	struct pa_options *opts;
	int use_args_check = 0;
	int helper_isrunning = 1;

	opts  = calloc_align(1, sizeof(*opts));
	opts->isprocsplitting = (ctx_p->flags[SPLITTING] == SM_PROCESS);
	opts->shm_mprotect    =  ctx_p->flags[SHM_MPROTECT];

	if (opts->isprocsplitting) {
		sigset_t sigset;
		sigemptyset(&sigset);

/*	Do not uncomment this. This causes handler closing on any terminal
	signal to parent process. In turn it causes: https://github.com/xaionaro/clsync/issues/104

		sigaddset(&sigset, SIGALRM);
		sigaddset(&sigset, SIGHUP);
		sigaddset(&sigset, SIGQUIT);
		sigaddset(&sigset, SIGTERM);
		sigaddset(&sigset, SIGINT);
*/
# ifndef __linux__
#  error There's no automatical mechanism that guarantees handler closing on non-linux. Don't use process splitting!
# endif

# ifdef __linux__
		sigaddset(&sigset, SIGCHLD);
# endif
		critical_on(pthread_sigmask(SIG_UNBLOCK, &sigset, NULL));

# ifndef __linux__
		critical_on(!parent_isalive());
# endif
	} else {
		register_blockthread();
		pthread_setname_np(pthread_self(), "clsync-helper");
	}
	cap_drop(ctx_p, ctx_p->caps);

	debug(2, "Syncing with the runner");
	pthread_mutex_lock(pthread_mutex_privileged_p);

	// Waiting for runner to get ready for signal
	pthread_mutex_lock(pthread_mutex_runner_p);
	pthread_mutex_unlock(pthread_mutex_runner_p);

	// Sending the signal that we're ready
	pthread_cond_signal(pthread_cond_runner_p);

	// The loop
	debug(2, "Running the loop");
	while (helper_isrunning) {
		errno = 0;

		// Waiting for command
		debug(10, "Waiting for command");
		if (opts->shm_mprotect) {
			mprotect((void *)cmd_p,		sizeof(*cmd_p),		PROT_WRITE);
			mprotect((void *)cmd_ret_p,	sizeof(*cmd_ret_p),	PROT_READ);
		}
# ifdef HL_LOCKS
		debug(25, "hl_lock_p->enabled == %i", hl_lock_p->enabled);
		if (!hl_lock_p->enabled || !hl_wait(
			HLLOCK_HANDLER
#  ifdef HL_LOCK_TRIES_AUTO
			, hl_lock_p->tries[HLLOCK_HANDLER]
#  endif
		)) {
			if (opts->isprocsplitting)
				critical_on(!parent_isalive());
# endif
# ifdef READWRITE_SIGNALLING
#  warning		READWRITE_SIGNALLING can cause process hanging on clsync shutdown
			read_inf(priv_read_fd, buf, 1);
# else
			int rc;
			while (1) {
				rc = pthread_cond_timedwait(pthread_cond_privileged_p, pthread_mutex_privileged_p, &wait_timeout);
				if (!rc)
					break;
				if (rc != ETIMEDOUT)
					critical("Got error while pthread_cond_timedwait()");
				debug(10, "pthread_cond_timedwait() timed out");

				if (opts->isprocsplitting)
					exit_on(!parent_isalive());

				{
					debug(20, "Resetting wait_timeout");
					struct timeval now;
					gettimeofday(&now, NULL);
					wait_timeout.tv_sec	= now.tv_sec  + SLEEP_SECONDS;
					wait_timeout.tv_nsec	= now.tv_usec * 1000;
				}
			}
# endif
# ifdef HL_LOCKS
			if (hl_lock_p->enabled)
				hl_setstate(HLLOCK_HANDLER, HLLS_WORKING);
		}
# endif
		if (opts->shm_mprotect) {
			mprotect((void *)cmd_p,		sizeof(*cmd_p),		PROT_READ);
			mprotect((void *)cmd_ret_p,	sizeof(*cmd_ret_p),	PROT_WRITE);
		}

		debug(10, "Got command %u (euid:egid => %i:%i)", cmd_p->action, geteuid(), getegid());

		if (!setup && cmd_p->action != PA_SETUP)
			critical("A try to use commands before PA_SETUP");

		switch (cmd_p->action) {
			case PA_SETUP: {
				debug(20, "PA_SETUP");
				if (setup)
					critical("Double privileged_handler setuping. It can be if somebody is trying to hack the clsync.");

				critical_on(pa_setup(opts, cmd_p->arg.ctx_p, &exec_uid, &exec_gid));
				mprotect(opts, sizeof(*opts), PROT_READ);
				use_args_check = cmd_p->arg.ctx_p->flags[CHECK_EXECVP_ARGS];
				cap_drop(ctx_p, ctx_p->caps);	// TODO: Find out why "permission denined" without this line
				setup++;
				critical_on(errno);
				break;
			}
			case PA_DIE:
				debug(20, "PA_DIE");
				helper_isrunning = 0;
				break;
			case PA_LSTAT64: {
				volatile struct pa_lstat64_arg *arg_p = &cmd_p->arg.lstat64;
				stat64_t *ret_buf = (void *)&cmd_ret_p->ret_buf.stat64;
				volatile const char *path =	opts->isprocsplitting 	?
								arg_p->path_buf 	:
								arg_p->path;

				debug(20, "PA_LSTAT64 (%s)", path);
				cmd_ret_p->ret = (void *)(long)lstat64((const char *)path, ret_buf);
				debug(21, "/PA_LSTAT64 => %i", (int)(long)cmd_ret_p->ret);
				break;
			}
			case PA_FTS_OPEN: {
				volatile struct pa_fts_open_arg *arg_p = &cmd_p->arg.fts_open;
				char *const *path_argv_p =(void *)(
						opts->isprocsplitting 	?
						arg_p->path_argv 	:
						arg_p->path_argv_p
					);

				debug(20, "PA_FTS_OPEN (%s)", *path_argv_p);
				if (arg_p->compar != NULL)
					critical("\"arg_p->compar != NULL\" (arg_p->compar == %p) is forbidden because may be used to run an arbitrary code in the privileged thread.", arg_p->compar);

				cmd_ret_p->ret = fts_open(path_argv_p, arg_p->options, NULL);
				debug(21, "/PA_FTS_OPEN => %p", cmd_ret_p->ret);
				break;
			}
			case PA_FTS_READ: {
				debug(20, "PA_FTS_READ(%p)", cmd_p->arg.void_v);
				FTSENT *ret = fts_read(cmd_p->arg.void_v);
				if (ret == NULL) {
					cmd_ret_p->ret = NULL;
					debug(10, "cmd_ret_p->ret == NULL");
					break;
				}
				if (!opts->isprocsplitting) {	// Is the thread-splitting?
					cmd_ret_p->ret = ret;
					break;
				}

				{	// Is the process splitting?
					struct pa_fts_read_ret *ret_buf = (void *)&cmd_ret_p->ret_buf.fts_read;
					memcpy(&ret_buf->ftsent, ret, sizeof(ret_buf->ftsent));
					cmd_ret_p->ret = &ret_buf->ftsent;
					debug(25, "fts_path == <%s>", ret_buf->fts_path);
					strncpy(ret_buf->fts_accpath, ret->fts_accpath, sizeof(ret_buf->fts_accpath));
					strncpy(ret_buf->fts_path,    ret->fts_path,    sizeof(ret_buf->fts_path));
					ret_buf->ftsent.fts_accpath = ret_buf->fts_accpath;
					ret_buf->ftsent.fts_path    = ret_buf->fts_path;
				}
				break;
			}
			case PA_FTS_CLOSE:
				debug(20, "PA_FTS_CLOSE");
				cmd_ret_p->ret = (void *)(long)fts_close(cmd_p->arg.void_v);
				break;
			case PA_INOTIFY_INIT:
				debug(20, "PA_INOTIFY_INIT");
				cmd_ret_p->ret = (void *)(long)inotify_init();
				break;
			case PA_INOTIFY_INIT1:
				debug(20, "PA_INOTIFY_INIT1");
				cmd_ret_p->ret = (void *)(long)inotify_init1(cmd_p->arg.uint32_v);
				break;
			case PA_INOTIFY_ADD_WATCH: {
				struct pa_inotify_add_watch_arg *arg_p = (void *)&cmd_p->arg.inotify_add_watch;
				const char *pathname = (opts->isprocsplitting ? arg_p->pathname : arg_p->pathname_p);
				debug(20, "PA_INOTIFY_ADD_WATCH(%u, <%s>, 0x%o)", arg_p->fd, pathname, arg_p->mask);
				cmd_ret_p->ret = (void *)(long)inotify_add_watch( arg_p->fd, pathname, arg_p->mask);
				break;
			}
			case PA_INOTIFY_RM_WATCH: {
				debug(20, "PA_INOTIFY_RM_WATCH");
				struct pa_inotify_rm_watch_arg *arg_p = (void *)&cmd_p->arg.inotify_rm_watch;
				cmd_ret_p->ret = (void *)(long)inotify_rm_watch(arg_p->fd, arg_p->wd);
				break;
			}
			case PA_FORK_EXECVP: {
				struct pa_fork_execvp_arg *arg_p = (void *)&cmd_p->arg.fork_execvp;
				const char *file;
				char *const *argv;

				if (opts->isprocsplitting) {
					file = arg_p->file;
					argv = arg_p->argv;
				} else {
					file = arg_p->file_p;
					argv = arg_p->argv_p;
				}

				debug(20, "PA_FORK_EXECVP (\"%s\", argv)", file);

				if (use_args_check)
					privileged_execvp_check_arguments(opts, file, argv);

				pid_t pid = fork();
				switch (pid) {
					case -1: 
						error("Cannot fork().");
						break;
					case  0: {
						int rc;
						(void)rc;	// anti-warning on ./configure --enable-debug=no
#ifdef ANTIPARANOID
						if (ctx_p->privileged_gid != exec_gid)
#endif
						{
							rc = setgid(exec_gid);
							debug(4, "setgid(%u) == %i", exec_gid, rc);
						}

#ifdef ANTIPARANOID
						if (ctx_p->privileged_uid != exec_uid)
#endif
						{
							rc = setuid(exec_uid);
							debug(4, "setuid(%u) == %i", exec_uid, rc);
						}

						debug(3, "execvp(\"%s\", argv)", file);
						exit(execvp(file, argv));
					}
				}
				cmd_ret_p->ret = (void *)(long)pid;
				debug(21, "/PA_FORK_EXECVP");
				break;
			}
			case PA_KILL_CHILD: {
				debug(20, "PA_KILL_CHILD");
				struct pa_kill_child_arg *arg_p = (void *)&cmd_p->arg.kill_child;
				cmd_ret_p->ret = (void *)(long)__privileged_kill_child_itself(arg_p->pid, arg_p->signal, arg_p->ignoreerrors);
				break;
			}
# ifdef CGROUP_SUPPORT
			case PA_CLSYNC_CGROUP_DEINIT: {
				debug(20, "PA_CLSYNC_CGROUP_DEINIT");
				/*
				 * That is strange, but setuid() doesn't work
				 * without fork() in case of enabled seccomp
				 * filter. So sorry for this hacky thing.
				 *
				 * TODO: fix that.
				 */
				int status;
				pid_t pid = fork();
				switch (pid) {
					case -1: 
						error("Cannot fork().");
						break;
					case  0:
						debug(4, "setgid(0) == %i", setgid(0));
						debug(4, "setuid(0) == %i", setuid(0));
						exit(clsync_cgroup_deinit(cmd_p->arg.void_v));
				}

				if (waitpid(pid, &status, 0) != pid) {
					switch (errno) {
						case ECHILD:
							debug(2, "Child %u has already died.", pid);
							break;
						default:
							error("Cannot waitid().");
							cmd_ret_p->_errno = errno;
							cmd_ret_p->ret    = (void *)(long)errno;
					}
				}

				// Return
				int exitcode = WEXITSTATUS(status);
				debug(3, "execution completed with exitcode %i", exitcode);

				cmd_ret_p->_errno = exitcode;
				cmd_ret_p->ret    = (void *)(long)exitcode;

				break;
			}
# endif
			case PA_WAITPID: {
				struct pa_waitpid_arg *arg_p = (void *)&cmd_p->arg.waitpid;
				debug(20, "PA_WAITPID(%u, 0x%o)", arg_p->pid, arg_p->options);
				cmd_ret_p->processing_longcmd = 1;
				cmd_ret_p->ret                = (void *)(long)waitpid(arg_p->pid, &arg_p->status, arg_p->options);
				cmd_ret_p->processing_longcmd = 0;
				break;
			}
			default:
				critical("Unknown command type \"%u\". It's a buffer overflow (which means a security problem) or just an internal error.");
		}

		cmd_ret_p->_errno = errno;
		debug(10, "Result: %p, errno: %u. Sending the signal to non-privileged thread/process.", cmd_ret_p->ret, cmd_ret_p->_errno);
# ifdef HL_LOCKS
		if (!hl_lock_p->enabled) {
# endif
# ifndef __linux__
			critical_on(!parent_isalive());
# endif
# ifdef READWRITE_SIGNALLING
			write_inf(nonp_write_fd, buf, 1);
# else
			critical_on (pthread_mutex_lock(pthread_mutex_action_signal_p));
			critical_on (pthread_mutex_unlock(pthread_mutex_action_signal_p));
			critical_on (pthread_cond_signal(pthread_cond_action_p));
# endif
# ifdef HL_LOCKS
		}
# endif
	}

	pa_unsetup(opts);
# ifdef HL_LOCKS
	hl_shutdown(HLLOCK_HANDLER);
# endif
	pthread_mutex_unlock(pthread_mutex_privileged_p);
	debug(2, "Finished");
	return 0;
}

static inline int privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
		int callid,
# endif
		enum privileged_action action,
		void **ret_p
	)
{
	int rc = 0;

# ifdef READWRITE_SIGNALLING
	char buf[1] = {0};
# endif
# ifdef HL_LOCK_TRIES_AUTO
	clock_t start_ticks;

	int isadjusting;
# endif
# ifdef HL_LOCKS
	debug(10, "(%u, %p): %i", action, ret_p, hl_lock_p->enabled);
# else
	debug(10, "(%u, %p)",     action, ret_p);
# endif

	pthread_mutex_lock(pthread_mutex_action_entrance_p);
# ifndef READWRITE_SIGNALLING
	debug(10, "Waiting the privileged thread/process to get prepared for signal");
#  ifdef HL_LOCKS
	if (hl_lock_p->enabled) {
		long long counter = 0;

		while (!hl_isanswered(HLLOCK_HANDLER)) {
			if (!helper_isalive_cache) {
				debug(1, "The privileged thread/process is dead (#0). Ignoring the command.");
				rc = ENOENT;
				goto privileged_action_end;
			}
			if (cmd_ret_p->processing_longcmd && ++counter > HL_LOCK_NONPRIV_TRIES)
				sleep(SLEEP_SECONDS);
		}
	} else {
#  endif
		critical_on(!helper_isalive_cache);
		pthread_mutex_lock(pthread_mutex_privileged_p);
		pthread_mutex_unlock(pthread_mutex_privileged_p);
#  ifdef HL_LOCKS
	}
#  endif
# endif
	if (!helper_isalive_cache) {
		debug(1, "The privileged thread/process is dead (#1). Ignoring the command.");
		rc = ENOENT;
		goto privileged_action_end;
	}

	cmd_p->action = action;
	debug(10, "Sending information (action == %i) to the privileged thread/process", action);
# ifdef HL_LOCK_TRIES_AUTO
	cmd_p->hl_lock_tries = hl_lock_p->tries[callid];

	if ((isadjusting = hl_lock_p->enabled)) {
		isadjusting = hl_lock_p->tries[callid];
		if (isadjusting) {
			isadjusting = ((double)fabs(hl_lock_p->tries_step[callid]-1) > (double)HL_LOCK_AUTO_K_FINISH);
			if (isadjusting) {
				isadjusting = !((++hl_lock_p->count[callid]) << (sizeof(hl_lock_p->count[callid])*CHAR_BIT - HL_LOCK_AUTO_INTERVAL));
				debug(11, "isadjusting == %u; hl_lock_p->tries_step[%i] == %lf; hl_lock_p->count[%i] == %lu", isadjusting, callid, hl_lock_p->tries_step[callid], callid, hl_lock_p->count[callid]);
				if (isadjusting)
					start_ticks = clock();
			}
		}
	}

# endif
# ifdef HL_LOCKS
	if (action == PA_DIE)
		hl_lock_p->enabled = 0;

	if (!hl_lock_p->enabled || !hl_signal(HLLOCK_HANDLER)) {
# endif
		critical_on(!helper_isalive_cache);
# ifdef READWRITE_SIGNALLING
		write_inf(priv_write_fd, buf, 1);
# else
#  ifdef HL_LOCKS
		if (hl_lock_p->enabled) {
			debug(10, "Waiting the privileged thread/process to get prepared for signal (by fallback)");
			critical_on (pthread_mutex_lock(pthread_mutex_privileged_p));
			critical_on (pthread_mutex_unlock(pthread_mutex_privileged_p));
		} else
#  endif
		critical_on (pthread_mutex_lock(pthread_mutex_action_signal_p));
		critical_on (pthread_cond_signal(pthread_cond_privileged_p));
# endif
# ifdef HL_LOCKS
	}
# endif

	if (action == PA_DIE)
		goto privileged_action_end;
	debug(10, "Waiting for the answer");

# ifdef HL_LOCKS
	if (hl_lock_p->enabled) {
		long long counter = 0;

		while (!hl_isanswered(HLLOCK_HANDLER)) {
			if (!helper_isalive_cache) {
				debug(1, "The privileged thread/process is dead (#2). Ignoring the command.");
				rc = ENOENT;
				goto privileged_action_end;
			}
			if (cmd_ret_p->processing_longcmd && ++counter > HL_LOCK_NONPRIV_TRIES)
				sleep(SLEEP_SECONDS);
		}

#  ifdef HL_LOCK_TRIES_AUTO
		if (isadjusting) {
			unsigned long delay = (long)clock() - (long)start_ticks;
			long diff  = delay - hl_lock_p->delay[callid];

			debug(13, "diff == %li; hl_lock_p->delay[%i] == %lu; delay == %lu; delay*HL_LOCK_AUTO_THREADHOLD == %lu", diff, callid, hl_lock_p->delay[callid], delay, delay*HL_LOCK_AUTO_THREADHOLD)

			if (diff && ((unsigned long)labs(diff) > (unsigned long)delay*HL_LOCK_AUTO_THREADHOLD)) {

				if (diff > 0)
					hl_lock_p->tries_step[callid] = 1/((hl_lock_p->tries_step[callid]-1)/HL_LOCK_AUTO_DECELERATION+1);

				hl_lock_p->delay[callid]  = delay;

				debug(12, "diff == %li; hl_lock_p->tries_step[%i] == %lf; hl_lock_p->delay[%i] == %lu", diff, callid, hl_lock_p->tries_step[callid], callid, hl_lock_p->delay[callid]);
			}
			hl_lock_p->tries[callid] *= hl_lock_p->tries_step[callid];

			if (hl_lock_p->tries[callid] > HL_LOCK_AUTO_LIMIT_HIGH)
				hl_lock_p->tries[callid] = HL_LOCK_AUTO_LIMIT_HIGH;

			debug(14, "hl_lock_p->tries[%i] == %lu", callid, hl_lock_p->tries[callid]);
		}
#  endif
	} else {
# endif
		critical_on(!helper_isalive_cache);
# ifdef READWRITE_SIGNALLING
		read_inf(nonp_read_fd, buf, 1);
# else
		critical_on (pthread_cond_wait(pthread_cond_action_p, pthread_mutex_action_signal_p));
# endif
# ifdef HL_LOCKS
	}
# endif

	if (ret_p != NULL)
		*ret_p = (void *)cmd_ret_p->ret;
	errno = cmd_ret_p->_errno;

privileged_action_end:
	debug(10, "Unlocking pthread_mutex_action_*");
# ifndef READWRITE_SIGNALLING
#  ifdef HL_LOCKS
	if (!hl_lock_p->enabled)
#  endif
		pthread_mutex_unlock(pthread_mutex_action_signal_p);
# endif

	pthread_mutex_unlock(pthread_mutex_action_entrance_p);

	return rc;
}

int __privileged_lstat64_procsplit(
		const char *path, stat64_t *buf
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	)
{
	void *ret = (void *)(long)-1;

	strcpy((char *)cmd_p->arg.lstat64.path_buf, path);

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			callid,
# endif
			PA_LSTAT64,
			&ret
		);

	memcpy(buf, (void *)&cmd_ret_p->ret_buf.stat64, sizeof(stat64_t));

	return (long)ret;
}

int __privileged_lstat64_threadsplit(
		const char *path, stat64_t *buf
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	)
{
	void *ret = (void *)(long)-1;

	cmd_p->arg.lstat64.path = path;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			callid,
# endif
			PA_LSTAT64,
			&ret
		);

	memcpy(buf, (void *)&cmd_ret_p->ret_buf.stat64, sizeof(stat64_t));

	return (long)ret;
}

FTS *__privileged_fts_open_procsplit(
		char *const *path_argv,
		int options,
		int (*compar)(const FTSENT **, const FTSENT **)
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	)
{
	void *ret = NULL;
	int i;

	i = 0;
	while (path_argv[i] != NULL) {
		cmd_p->arg.fts_open.path_argv[i] = (void *)cmd_p->arg.fts_open._path_argv[i];
		debug(25, "path_argv[%i] == <%s> (%p) -> %p", i, path_argv[i], path_argv[i], cmd_p->arg.fts_open.path_argv[i]);
		strncpy(cmd_p->arg.fts_open.path_argv[i], path_argv[i], sizeof(cmd_p->arg.fts_open._path_argv[i]));
		i++;
		critical_on(i >= MAXARGUMENTS);
	}
	cmd_p->arg.fts_open.path_argv[i]	= NULL;
	cmd_p->arg.fts_open.options		= options;
	cmd_p->arg.fts_open.compar		= compar;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			callid,
# endif
			PA_FTS_OPEN,
			&ret
		);

	return ret;
}

FTS *__privileged_fts_open_threadsplit(
		char *const *path_argv,
		int options,
		int (*compar)(const FTSENT **, const FTSENT **)
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	)
{
	void *ret = NULL;

	cmd_p->arg.fts_open.path_argv_p		= path_argv;
	cmd_p->arg.fts_open.options		= options;
	cmd_p->arg.fts_open.compar		= compar;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			callid,
# endif
			PA_FTS_OPEN,
			&ret
		);

	return ret;
}

FTSENT *__privileged_fts_read(
		FTS *ftsp
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	)
{
	void *ret = NULL;
	cmd_p->arg.void_v = ftsp;
	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			callid,
# endif
			PA_FTS_READ,
			&ret
		);
	return ret;
}

int __privileged_fts_close(
		FTS *ftsp
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	)
{
	void *ret = (void *)(long)-1;
	cmd_p->arg.void_v = ftsp;
	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			callid,
# endif
			PA_FTS_CLOSE,
			&ret
		);
	return (long)ret;
}

int __privileged_inotify_init() {
	void *ret = (void *)(long)-1;
	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_INOTIFY_INIT,
			&ret
		);
	return (long)ret;
}

int __privileged_inotify_init1(int flags) {
	void *ret = (void *)(long)-1;
	cmd_p->arg.uint32_v = flags;
	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_INOTIFY_INIT1,
			&ret
		);
	return (long)ret;
}

int __privileged_inotify_add_watch_threadsplit(
		int fd,
		const char *pathname,
		uint32_t mask
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	)
{
	debug(25, "(%i, <%s>, o%o, ?)", fd, pathname, mask);
	void *ret = (void *)(long)-1;

	cmd_p->arg.inotify_add_watch.pathname_p	= pathname;
	cmd_p->arg.inotify_add_watch.fd		= fd;
	cmd_p->arg.inotify_add_watch.mask	= mask;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			callid,
# endif
			PA_INOTIFY_ADD_WATCH,
			&ret
		);

	return (long)ret;
}

int __privileged_inotify_add_watch_procsplit(
		int fd,
		const char *pathname,
		uint32_t mask
# ifdef HL_LOCK_TRIES_AUTO
		, int callid
# endif
	)
{
	debug(25, "(%i, <%s>, o%o, ?)", fd, pathname, mask);
	void *ret = (void *)(long)-1;

	strncpy((void *)cmd_p->arg.inotify_add_watch.pathname, pathname, sizeof(cmd_p->arg.inotify_add_watch.pathname));
	cmd_p->arg.inotify_add_watch.fd		= fd;
	cmd_p->arg.inotify_add_watch.mask	= mask;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			callid,
# endif
			PA_INOTIFY_ADD_WATCH,
			&ret
		);

	return (long)ret;
}

int __privileged_inotify_rm_watch(
		int fd,
		int wd
	)
{
	void *ret = (void *)(long)-1;

	cmd_p->arg.inotify_rm_watch.fd	= fd;
	cmd_p->arg.inotify_rm_watch.wd	= wd;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_INOTIFY_RM_WATCH,
			&ret
		);

	return (long)ret;
}

# ifdef CGROUP_SUPPORT
int __privileged_clsync_cgroup_deinit(ctx_t *ctx_p)
{
	void *ret = (void *)(long)-1;

	cmd_p->arg.ctx_p = ctx_p;

	privileged_action(
#  ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
#  endif
			PA_CLSYNC_CGROUP_DEINIT,
			&ret
		);

	return (long)ret;
}
# endif

int __privileged_fork_setuid_execvp_procsplit(
		const char *file,
		char *const argv[]
	)
{
	int i;
	void *ret = (void *)(long)-1;

	strncpy((void *)cmd_p->arg.fork_execvp.file, file, sizeof(cmd_p->arg.fork_execvp.file));

	i=0;
	while (argv[i] != NULL) {
		cmd_p->arg.fork_execvp.argv[i] = (void *)cmd_p->arg.fork_execvp._argv[i];
		strncpy(cmd_p->arg.fork_execvp.argv[i], argv[i], sizeof(cmd_p->arg.fork_execvp._argv[i]));
		i++;
		critical_on(i >= MAXARGUMENTS);
	}
	cmd_p->arg.fork_execvp.argv[i] = NULL;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_FORK_EXECVP,
			&ret
		);

	return (long)ret;
}

int __privileged_fork_setuid_execvp_threadsplit(
		const char *file,
		char *const argv[]
	)
{
	void *ret = (void *)(long)-1;

	cmd_p->arg.fork_execvp.file_p = file;
	cmd_p->arg.fork_execvp.argv_p = argv;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_FORK_EXECVP,
			&ret
		);

	return (long)ret;
}

int __privileged_kill_child_wrapper(pid_t pid, int signal, char ignoreerrors)
{
	void *ret = (void *)(long)-1;

	cmd_p->arg.kill_child.pid          = pid;
	cmd_p->arg.kill_child.signal       = signal;
	cmd_p->arg.kill_child.ignoreerrors = ignoreerrors;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_KILL_CHILD,
			&ret);

	return (long)ret;
}

pid_t __privileged_waitpid(pid_t pid, int *status, int options)
{
	void *ret = (void *)(long)-1;

	cmd_p->arg.waitpid.pid     = pid;
	cmd_p->arg.waitpid.options = options;

	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_WAITPID,
			&ret);

	if (status != NULL)
		*status = cmd_p->arg.waitpid.status;

	return (long)ret;
}

#endif

uid_t __privileged_fork_execvp_uid;
gid_t __privileged_fork_execvp_gid;
int __privileged_fork_execvp(const char *file, char *const argv[])
{
	debug(4, "");
	pid_t pid = fork();
	switch (pid) {
		case -1: 
			error("Cannot fork().");
			return -1;
		case  0: {
			int rc;
			(void)rc;	// anti-warning on ./configure --enable-debug=no

			rc = setgid(__privileged_fork_execvp_gid);
			debug(4, "setgid(%u) == %i", __privileged_fork_execvp_gid, rc);

			rc = setuid(__privileged_fork_execvp_uid);
			debug(4, "setuid(%u) == %i", __privileged_fork_execvp_uid, rc);

			errno = 0;
			execvp(file, argv);
			exit(errno);
		}
	}

	return pid;
}

#ifdef CAPABILITIES_SUPPORT
#define pthread_mutex_init_smart(mutex_p) _pthread_mutex_init_smart(ctx_p->flags[SPLITTING]==SM_PROCESS, mutex_p)
static inline int _pthread_mutex_init_smart(int isshared, pthread_mutex_t **mutex_p) {
	int rc;
	pthread_mutex_t *mutex, initial = PTHREAD_MUTEX_INITIALIZER;
	if (isshared)
		return pthread_mutex_init_shared(mutex_p);

	mutex = xmalloc(sizeof(*mutex));
	memcpy(mutex, &initial, sizeof(*mutex));

	rc = pthread_mutex_init(mutex, NULL);
	if (rc)
		return rc;

	*mutex_p = mutex;

	return rc;
}

#define pthread_mutex_destroy_smart(mutex_p) _pthread_mutex_destroy_smart(ctx_p->flags[SPLITTING]==SM_PROCESS, mutex_p)
static inline int _pthread_mutex_destroy_smart(int isshared, pthread_mutex_t *mutex_p) {
	int rc;
	if (isshared)
		return pthread_mutex_destroy_shared(mutex_p);

	rc = pthread_mutex_destroy(mutex_p);
	if (rc)
		return rc;

	free(mutex_p);

	return 0;
}

#define pthread_cond_init_smart(cond_p) _pthread_cond_init_smart(ctx_p->flags[SPLITTING]==SM_PROCESS, cond_p)
static inline int _pthread_cond_init_smart(int isshared, pthread_cond_t **cond_p) {
	int rc;
	pthread_cond_t *cond, initial = PTHREAD_COND_INITIALIZER;
	if (isshared)
		return pthread_cond_init_shared(cond_p);

	cond = xmalloc(sizeof(*cond));
	memcpy(cond, &initial, sizeof(*cond));

	rc = pthread_cond_init(cond, NULL);
	if (rc)
		return rc;

	*cond_p = cond;

	return rc;
}

#define pthread_cond_destroy_smart(cond_p) _pthread_cond_destroy_smart(ctx_p->flags[SPLITTING]==SM_PROCESS, cond_p)
static inline int _pthread_cond_destroy_smart(int isshared, pthread_cond_t *cond_p) {
	int rc;
	if (isshared)
		return pthread_cond_destroy_shared(cond_p);

	rc = pthread_cond_destroy(cond_p);
	if (rc)
		return rc;

	free(cond_p);

	return 0;
}
#endif

int privileged_init(ctx_t *ctx_p)
{
#ifdef READWRITE_SIGNALLING
	int pipefds[2];
#endif

#ifdef CAPABILITIES_SUPPORT
	if (ctx_p->flags[SPLITTING] == SM_OFF) {
#endif

		_privileged_fork_execvp		= __privileged_fork_execvp;

		__privileged_fork_execvp_uid	= ctx_p->synchandler_uid;
		__privileged_fork_execvp_gid	= ctx_p->synchandler_gid;

		_privileged_kill_child		= __privileged_kill_child_itself;

#ifdef CAPABILITIES_SUPPORT
		_privileged_lstat64		= (typeof(_privileged_lstat64))			lstat64;
		_privileged_fts_open		= (typeof(_privileged_fts_open))		fts_open;
		_privileged_fts_read		= (typeof(_privileged_fts_read))		fts_read;
		_privileged_fts_close		= (typeof(_privileged_fts_close))		fts_close;
		_privileged_inotify_init	= (typeof(_privileged_inotify_init))		inotify_init;
		_privileged_inotify_init1	= (typeof(_privileged_inotify_init1))		inotify_init1;
		_privileged_inotify_add_watch	= (typeof(_privileged_inotify_add_watch))	inotify_add_watch;
		_privileged_inotify_rm_watch	= (typeof(_privileged_inotify_rm_watch))	inotify_rm_watch;
# ifdef CGROUP_SUPPORT
		_privileged_clsync_cgroup_deinit= (typeof(_privileged_clsync_cgroup_deinit))	clsync_cgroup_deinit;
# endif
		_privileged_waitpid		= (typeof(_privileged_waitpid))			waitpid;

		cap_drop(ctx_p, ctx_p->caps);
#endif

		return 0;

#ifdef CAPABILITIES_SUPPORT
	}

	_privileged_fts_read		= __privileged_fts_read;
	_privileged_fts_close		= __privileged_fts_close;
	_privileged_inotify_init	= __privileged_inotify_init;
	_privileged_inotify_init1	= __privileged_inotify_init1;
	_privileged_inotify_rm_watch	= __privileged_inotify_rm_watch;
	_privileged_kill_child		= __privileged_kill_child_wrapper;
# ifdef CGROUP_SUPPORT
	_privileged_clsync_cgroup_deinit= __privileged_clsync_cgroup_deinit;
# endif
	_privileged_waitpid		= __privileged_waitpid;

	SAFE ( pthread_mutex_init_smart(&pthread_mutex_privileged_p),		return errno;);
	SAFE ( pthread_mutex_init_smart(&pthread_mutex_action_entrance_p),	return errno;);
	SAFE ( pthread_mutex_init_smart(&pthread_mutex_action_signal_p),	return errno;);
	SAFE ( pthread_mutex_init_smart(&pthread_mutex_runner_p),		return errno;);
	SAFE ( pthread_cond_init_smart (&pthread_cond_privileged_p),		return errno;);
	SAFE ( pthread_cond_init_smart (&pthread_cond_action_p),		return errno;);
	SAFE ( pthread_cond_init_smart (&pthread_cond_runner_p),		return errno;);

# ifdef READWRITE_SIGNALLING
	SAFE ( pipe2(pipefds, O_CLOEXEC), 				return errno;);
	priv_read_fd  = pipefds[0];
	priv_write_fd = pipefds[1];

	SAFE ( pipe2(pipefds, O_CLOEXEC), 				return errno;);
	nonp_read_fd  = pipefds[0];
	nonp_write_fd = pipefds[1];
# endif

	SAFE ( pthread_mutex_lock(pthread_mutex_runner_p),		return errno;);

# ifdef UNSHARE_SUPPORT
	unshare(CLONE_NEWIPC);
# endif

	switch (ctx_p->flags[SPLITTING]) {
		case SM_THREAD: {
			_privileged_lstat64		= __privileged_lstat64_threadsplit;
			_privileged_fts_open		= __privileged_fts_open_threadsplit;
			_privileged_inotify_add_watch	= __privileged_inotify_add_watch_threadsplit;
			_privileged_fork_execvp		= __privileged_fork_setuid_execvp_threadsplit;

			cmd_p     = calloc_align(1, sizeof(*cmd_p));
			cmd_ret_p = calloc_align(1, sizeof(*cmd_ret_p));
# ifdef HL_LOCKS
			hl_lock_p = calloc_align(1, sizeof(*hl_lock_p));
			hl_lock_init(hl_lock_p);
# endif

			// Running the privileged thread
			SAFE ( pthread_create(&privileged_thread, NULL, (void *(*)(void *))privileged_handler, ctx_p), return errno);

			if (ctx_p->flags[FORGET_PRIVTHREAD_INFO])
				privileged_thread = 0;
			break;
		}
		case SM_PROCESS: {
			_privileged_lstat64		= __privileged_lstat64_procsplit;
			_privileged_fts_open		= __privileged_fts_open_procsplit;
			_privileged_inotify_add_watch	= __privileged_inotify_add_watch_procsplit;
			_privileged_fork_execvp		= __privileged_fork_setuid_execvp_procsplit;

			cmd_p     = shm_calloc(1, sizeof(*cmd_p));
			cmd_ret_p = shm_calloc(1, sizeof(*cmd_ret_p));
# ifdef HL_LOCKS
			hl_lock_p = shm_calloc(1, sizeof(*hl_lock_p));
			hl_lock_init(hl_lock_p);
# endif

			// Running the privileged helper
			SAFE ( (helper_pid = fork_helper()) == -1,	return errno);
			if (!helper_pid) {
				if (ctx_p->privileged_gid != ctx_p->gid) {
//					SAFE ( cap_enable(CAP_TO_MASK(CAP_SETGID)), return errno; );

					debug(3, "[privileged] Trying to set real gid to %i (ctx_p->privileged_gid)", ctx_p->privileged_gid);
					SAFE( setgid(ctx_p->privileged_gid),	return errno);
				}

				if (ctx_p->privileged_uid != ctx_p->uid) {
//					SAFE ( cap_enable(CAP_TO_MASK(CAP_SETUID)), return errno; );

					debug(3, "[privileged] Trying to set real uid to %i (ctx_p->privileged_uid)", ctx_p->privileged_uid);
					SAFE( setuid(ctx_p->privileged_uid),	return errno);
				}

				exit(privileged_handler(ctx_p));
			}
			break;
		}
		default:
			critical("Invalid ctx_p->flags[SPLITTING]: %i", ctx_p->flags[SPLITTING]);
	}

	if (ctx_p->flags[GID] || ctx_p->flags[UID])
		SAFE( seteuid(0), {error("Not enough permission to start a privileged thread/fork"); return errno;});

	if (ctx_p->flags[GID]) {
		SAFE( setegid(0), return errno);
		debug(3, "[non-privileged] Trying to drop real gid %i (ctx_p->gid)", getgid(), ctx_p->gid);
		SAFE( setgid(ctx_p->gid), return errno );
	}

	if (ctx_p->flags[UID]) {
		debug(3, "[non-privileged] Trying to drop real uid %i (ctx_p->uid)", getuid(), ctx_p->uid);
		SAFE( setuid(ctx_p->uid), return errno );
	}

# ifdef HL_LOCKS
	if (ncpus == 1)
		hl_shutdown(HLLOCK_HANDLER);
# endif

	critical_on(!helper_isalive());

# ifdef UNSHARE_SUPPORT
	// The rest routines
	if (ctx_p->flags[DETACH_NETWORK] == DN_NONPRIVILEGED) {
		SAFE ( cap_enable(CAP_TO_MASK(CAP_SYS_ADMIN)),	return errno; );
		SAFE ( unshare(CLONE_NEWNET),			return errno; );
	}
# endif
	SAFE ( cap_drop(ctx_p, 0),				return errno; );

	debug(4, "Waiting for the privileged thread to get prepared");
	pthread_cond_wait(pthread_cond_runner_p, pthread_mutex_runner_p);
	pthread_mutex_unlock(pthread_mutex_runner_p);

	debug(4, "Sending the settings (exec_uid == %u; exec_gid == %u)", ctx_p->synchandler_uid, ctx_p->synchandler_gid);
	cmd_p->arg.ctx_p = ctx_p;
	privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_SETUP,
			NULL
		);

	SAFE (pthread_mutex_destroy_smart(pthread_mutex_runner_p),	return errno;);
	SAFE (pthread_cond_destroy_smart(pthread_cond_runner_p),	return errno;);

# ifdef SECCOMP_SUPPORT
	if (ctx_p->flags[SECCOMP_FILTER])
		nonprivileged_seccomp_init(ctx_p);
# endif

	debug(5, "Finish");
	return 0;
#endif
}


int privileged_deinit(ctx_t *ctx_p)
{
	int ret = 0;
#ifdef CAPABILITIES_SUPPORT

	if (ctx_p->flags[SPLITTING] == SM_OFF)
		return 0;

	SAFE ( privileged_action(
# ifdef HL_LOCK_TRIES_AUTO
			PC_DEFAULT,
# endif
			PA_DIE,
			NULL
		),
		ret = errno
	);

# ifdef HL_LOCK_TRIES_AUTO
{
	int i=0;
	while (i < PC_MAX) {
		debug(1, "hl_lock_p->tries[%i] == %lu", i, hl_lock_p->tries[i]);
		i++;
	}
}
# endif

# ifdef HL_LOCKS
	hl_shutdown(HLLOCK_HANDLER);
# endif

	switch (ctx_p->flags[SPLITTING]) {
		case SM_THREAD: {
			if (ctx_p->flags[FORGET_PRIVTHREAD_INFO]) {
				pthread_mutex_lock(pthread_mutex_privileged_p);
				pthread_mutex_unlock(pthread_mutex_privileged_p);
			} else {
				SAFE ( pthread_join(privileged_thread, NULL),  ret = errno );
			}
			free((void *)cmd_p);
			free((void *)cmd_ret_p);
# ifdef HL_LOCKS
			free((void *)hl_lock_p);
# endif
			break;
		}
		case SM_PROCESS: {
			int status;
			if (!ctx_p->flags[SECCOMP_FILTER]) {
				if (!__privileged_kill_child_itself(helper_pid, SIGKILL, 1)) {
					debug(9, "waitpid(%u, ...)", helper_pid);
					waitpid(helper_pid, &status, 0);
				}
			}
			shm_free((void *)cmd_p);
			shm_free((void *)cmd_ret_p);
# ifdef HL_LOCKS
			shm_free((void *)hl_lock_p);
# endif
			break;
		}
	}
/*
	SAFE ( pthread_mutex_destroy_smart(pthread_mutex_privileged_p),		ret = errno );
	SAFE ( pthread_mutex_destroy_smart(pthread_mutex_action_entrance_p),	ret = errno );
	SAFE ( pthread_mutex_destroy_smart(pthread_mutex_action_signal_p),	ret = errno );
	SAFE ( pthread_cond_destroy_smart(pthread_cond_privileged_p),		ret = errno );
	SAFE ( pthread_cond_destroy_smart(pthread_cond_action_p),		ret = errno );
*/
#endif

	debug(2, "endof privileged_deinit()");
	return ret;
}


