/*
    clsync - file tree sync utility based on inotify/kqueue/bsm

    Copyright (C) 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"

#ifdef CAPABILITIES_SUPPORT
#	include <sys/capability.h>	// for capset()/capget() for --preserve-file-access
#endif
#if defined(__linux__) | defined(CAPABILITIES_SUPPORT)
#	include <sys/prctl.h>		// for prctl() for --preserve-fil-access
#endif

#include <pwd.h>	// getpwnam()
#include <grp.h>	// getgrnam()
#include <glib.h>	// gkf


#ifdef UNSHARE_SUPPORT
#	include <sched.h>	// unshare()
#endif
#ifdef GETMNTENT_SUPPORT
#	include <mntent.h>	// getmntent()
#	include <sys/mount.h>	// umount2()
#endif

#include "error.h"
#include "stringex.h"
#include "sync.h"
#include "malloc.h"
#include "cluster.h"
#include "fileutils.h"
#include "socket.h"
#include "syscalls.h"
#include "rules.h"
#if CGROUP_SUPPORT
#	include "cgroup.h"
#endif
#include "posix-hacks.h"

//#include "revision.h"

static const struct option long_options[] =
{
	{"watch-dir",		required_argument,	NULL,	WATCHDIR},
	{"sync-handler",	required_argument,	NULL,	SYNCHANDLER},
	{"--",			required_argument,	NULL,	SYNCHANDLERARGS0},
	{"---",			required_argument,	NULL,	SYNCHANDLERARGS1},
	{"rules-file",		required_argument,	NULL,	RULESFILE},
	{"destination-dir",	required_argument,	NULL,	DESTDIR},
	{"mode",		required_argument,	NULL,	MODE},
	{"socket",		required_argument,	NULL,	SOCKETPATH},
	{"socket-auth",		required_argument,	NULL,	SOCKETAUTH},
	{"socket-mod",		required_argument,	NULL,	SOCKETMOD},
	{"socket-own",		required_argument,	NULL,	SOCKETOWN},
	{"status-file",		required_argument,	NULL,	STATUSFILE},

	{"background",		optional_argument,	NULL,	BACKGROUND},
	{"config-file",		required_argument,	NULL,	CONFIGFILE},
	{"config-block",	required_argument,	NULL,	CONFIGBLOCK},
	{"config-block-inherits",required_argument,	NULL,	CONFIGBLOCKINHERITS},
	{"custom-signals",	required_argument,	NULL,	CUSTOMSIGNALS},
	{"pid-file",		required_argument,	NULL,	PIDFILE},
	{"uid",			required_argument,	NULL,	UID},
	{"gid",			required_argument,	NULL,	GID},
	{"privileged-uid",	required_argument,	NULL,	PRIVILEGEDUID},
	{"privileged-gid",	required_argument,	NULL,	PRIVILEGEDGID},
	{"sync-handler-uid",	required_argument,	NULL,	SYNCHANDLERUID},
	{"sync-handler-gid",	required_argument,	NULL,	SYNCHANDLERGID},
	{"chroot",		required_argument,	NULL,	CHROOT},
#ifdef PIVOTROOT_OPT_SUPPORT
	{"pivot-root",		required_argument,	NULL,	PIVOT_ROOT},
#endif
#ifdef UNSHARE_SUPPORT
	{"detach-network",	required_argument,	NULL,	DETACH_NETWORK},
	{"detach-ipc",		required_argument,	NULL,	DETACH_IPC},
	{"detach-miscellanea",	optional_argument,	NULL,	DETACH_MISCELLANEA},
#endif
#ifdef CAPABILITIES_SUPPORT
# ifdef SECCOMP_SUPPORT
	{"secure-splitting",	no_argument,		NULL,	SECURESPLITTING},
# endif
	{"splitting",		required_argument,	NULL,	SPLITTING},
	{"check-execvp-args",	optional_argument,	NULL,	CHECK_EXECVP_ARGS},
	{"add-permitted-hook-files",required_argument,	NULL,	ADDPERMITTEDHOOKFILES},
# ifdef SECCOMP_SUPPORT
	{"seccomp-filter",	optional_argument,	NULL,	SECCOMP_FILTER},
# endif
	{"forget-privthread-info",optional_argument,	NULL,	FORGET_PRIVTHREAD_INFO},
	{"permit-mprotect",	optional_argument,	NULL,	PERMIT_MPROTECT},
	{"shm-mprotect",	optional_argument,	NULL,	SHM_MPROTECT},
#endif
#ifdef GETMNTENT_SUPPORT
	{"mountpoints",		required_argument,	NULL,	MOUNTPOINTS},
#endif
#ifdef CAPABILITIES_SUPPORT
	{"preserve-capabilities",required_argument,	NULL,	CAP_PRESERVE},
	{"inherit-capabilities",optional_argument,	NULL,	CAPS_INHERIT},
#endif
#ifdef CGROUP_SUPPORT
	{"forbid-devices",	optional_argument,	NULL,	FORBIDDEVICES},
	{"cgroup-group-name",	required_argument,	NULL,	CG_GROUPNAME},
#endif
	{"threading",		required_argument,	NULL,	THREADING},
	{"retries",		required_argument,	NULL,	RETRIES},
	{"ignore-failures",	optional_argument,	NULL,	IGNOREFAILURES},
	{"exit-on-sync-skipping",optional_argument,	NULL,	EXITONSYNCSKIP},
	{"output",		required_argument,	NULL,	OUTPUT_METHOD},
	{"one-file-system",	optional_argument,	NULL,	ONEFILESYSTEM},
	{"exclude-mount-points",optional_argument,	NULL,	EXCLUDEMOUNTPOINTS},
#ifdef CLUSTER_SUPPORT
	{"cluster-iface",	required_argument,	NULL,	CLUSTERIFACE},		// Not implemented, yet
	{"cluster-ip",		required_argument,	NULL,	CLUSTERMCASTIPADDR},	// Not implemented, yet
	{"cluster-port",	required_argument,	NULL,	CLUSTERMCASTIPPORT},	// Not implemented, yet
	{"cluster-timeout",	required_argument,	NULL,	CLUSTERTIMEOUT},	// Not implemented, yet
	{"cluster-node-name",	required_argument,	NULL,	CLUSTERNODENAME},	// Not implemented, yet
	{"cluster-hash-dl-min",	required_argument,	NULL,	CLUSTERHDLMIN},
	{"cluster-hash-dl-max",	required_argument,	NULL,	CLUSTERHDLMAX},
	{"cluster-scan-dl-max",	required_argument,	NULL,	CLUSTERSDLMAX},
#endif
	{"max-iterations",	required_argument,	NULL,	MAXITERATIONS},
	{"standby-file",	required_argument,	NULL,	STANDBYFILE},
	{"modification-signature",required_argument,	NULL,	MODSIGN},
	{"timeout-sync",	required_argument,	NULL,	SYNCTIMEOUT},
	{"delay-sync",		required_argument,	NULL,	SYNCDELAY},
	{"delay-collect",	required_argument,	NULL,	DELAY},
	{"delay-collect-bigfile",required_argument,	NULL,	BFILEDELAY},
	{"threshold-bigfile",	required_argument,	NULL,	BFILETHRESHOLD},
	{"cancel-syscalls",	required_argument,	NULL,	CANCEL_SYSCALLS},
	{"lists-dir",		required_argument,	NULL,	OUTLISTSDIR},
	{"have-recursive-sync",	optional_argument,	NULL,	HAVERECURSIVESYNC},
	{"synclist-simplify",	optional_argument,	NULL,	SYNCLISTSIMPLIFY},
#ifdef AUTORULESW
	{"auto-add-rules-w",	optional_argument,	NULL,	AUTORULESW},
#endif
	{"rsync-inclimit",	required_argument,	NULL,	RSYNCINCLIMIT},
	{"rsync-prefer-include",optional_argument,	NULL,	RSYNCPREFERINCLUDE},
	{"ignore-exitcode",	required_argument,	NULL,	IGNOREEXITCODE},
	{"dont-unlink-lists",	optional_argument,	NULL,	DONTUNLINK},
	{"fts-experimental-optimization", optional_argument,	NULL,	FTS_EXPERIMENTAL_OPTIMIZATION},
	{"full-initialsync",	optional_argument,	NULL,	INITFULL},
	{"only-initialsync",	optional_argument,	NULL,	ONLYINITSYNC},
	{"skip-initialsync",	optional_argument,	NULL,	SKIPINITSYNC},
	{"exit-on-no-events",	optional_argument,	NULL,	EXITONNOEVENTS},
	{"exit-hook",		required_argument,	NULL,	EXITHOOK},
	{"pre-exit-hook",	required_argument,	NULL,	PREEXITHOOK},
	{"verbose",		optional_argument,	NULL,	VERBOSE},
	{"debug",		optional_argument,	NULL,	DEBUG},
	{"dump-dir",		required_argument,	NULL,	DUMPDIR},
	{"quiet",		optional_argument,	NULL,	QUIET},
	{"monitor",		required_argument,	NULL,	MONITOR},
	{"label",		required_argument,	NULL,	LABEL},
	{"help",		optional_argument,	NULL,	HELP},
	{"version",		optional_argument,	NULL,	SHOW_VERSION},

	{NULL,			0,			NULL,	0}
};

#ifdef UNSHARE_SUPPORT
static char *const detachnetworkways[] = {
	[DN_OFF]		= "off",
	[DN_NONPRIVILEGED]	= "non-privileged",
	[DN_EVERYWHERE]		= "everywhere",
	NULL,
};
#endif

#ifdef PIVOTROOT_OPT_SUPPORT
static char *const pivotrootways[] = {
	[PW_OFF]	= "off",
	[PW_DIRECT]	= "direct",
	[PW_AUTO]	= "auto",
	[PW_AUTORO]	= "auto-ro",
	NULL,
};
#endif

enum xstatfield {
	X_STAT_FIELD_RESET = 0,
	X_STAT_FIELD_DEV,
	X_STAT_FIELD_INO,
	X_STAT_FIELD_MODE,
	X_STAT_FIELD_NLINK,
	X_STAT_FIELD_UID,
	X_STAT_FIELD_GID,
	X_STAT_FIELD_RDEV,
	X_STAT_FIELD_SIZE,
	X_STAT_FIELD_BLKSIZE,
	X_STAT_FIELD_BLOCKS,
	X_STAT_FIELD_ATIME,
	X_STAT_FIELD_MTIME,
	X_STAT_FIELD_CTIME,
	X_STAT_FIELD_ALL,
};

uint32_t xstatfield_to_statfield[] = {
	[X_STAT_FIELD_RESET]		= STAT_FIELD_RESET,
	[X_STAT_FIELD_DEV]		= STAT_FIELD_DEV,
	[X_STAT_FIELD_INO]		= STAT_FIELD_INO,
	[X_STAT_FIELD_MODE]		= STAT_FIELD_MODE,
	[X_STAT_FIELD_NLINK]		= STAT_FIELD_NLINK,
	[X_STAT_FIELD_UID]		= STAT_FIELD_UID,
	[X_STAT_FIELD_GID]		= STAT_FIELD_GID,
	[X_STAT_FIELD_RDEV]		= STAT_FIELD_RDEV,
	[X_STAT_FIELD_SIZE]		= STAT_FIELD_SIZE,
	[X_STAT_FIELD_BLKSIZE]		= STAT_FIELD_BLKSIZE,
	[X_STAT_FIELD_BLOCKS]		= STAT_FIELD_BLOCKS,
	[X_STAT_FIELD_ATIME]		= STAT_FIELD_ATIME,
	[X_STAT_FIELD_MTIME]		= STAT_FIELD_MTIME,
	[X_STAT_FIELD_CTIME]		= STAT_FIELD_CTIME,
	[X_STAT_FIELD_ALL]		= STAT_FIELD_ALL,
};

static char *const stat_fields[] = {
	[X_STAT_FIELD_RESET]		= "",
	[X_STAT_FIELD_DEV]		= "dev",
	[X_STAT_FIELD_INO]		= "ino",
	[X_STAT_FIELD_MODE]		= "mode",
	[X_STAT_FIELD_NLINK]		= "nlink",
	[X_STAT_FIELD_UID]		= "uid",
	[X_STAT_FIELD_GID]		= "gid",
	[X_STAT_FIELD_RDEV]		= "rdev",
	[X_STAT_FIELD_SIZE]		= "size",
	[X_STAT_FIELD_BLKSIZE]		= "blksize",
	[X_STAT_FIELD_BLOCKS]		= "blocks",
	[X_STAT_FIELD_ATIME]		= "atime",
	[X_STAT_FIELD_MTIME]		= "mtime",
	[X_STAT_FIELD_CTIME]		= "ctime",
	[X_STAT_FIELD_ALL]		= "*",
	NULL
};

enum x_csc_bm {
	X_CSC_RESET = 0,
	X_CSC_MON_STAT,
};

uint32_t xcsc_to_csc[] = {
	[X_CSC_RESET]			= CSC_RESET,
	[X_CSC_MON_STAT]		= CSC_MON_STAT,
};

static char *const syscalls_bitmask[] = {
	[X_CSC_RESET]			= "",
	[X_CSC_MON_STAT]		= "mon_stat",	// disable {l,}stat{,64}()-s in mon_*.c
	NULL
};

#ifdef CAPABILITIES_SUPPORT

enum x_capabilities {
	X_CAP_RESET = 0,
	X_CAP_DAC_READ_SEARCH,
	X_CAP_SETUID,
	X_CAP_SETGID,
	X_CAP_KILL,

	X_CAP_MAX
};
__u32 xcap_to_cap[] = {
	[X_CAP_DAC_READ_SEARCH] = CAP_DAC_READ_SEARCH,
	[X_CAP_SETUID] 		= CAP_SETUID,
	[X_CAP_SETGID] 		= CAP_SETGID,
	[X_CAP_KILL] 		= CAP_KILL,
};
static char *const capabilities[] = {
	[X_CAP_RESET]		= "",
	[X_CAP_DAC_READ_SEARCH]	= "CAP_DAC_READ_SEARCH",
	[X_CAP_SETUID]		= "CAP_SETUID",
	[X_CAP_SETGID]		= "CAP_SETGID",
	[X_CAP_KILL]		= "CAP_KILL",
	NULL
};
#define XCAP_TO_CAP(x) (xcap_to_cap[x])

static char *const capsinherits[] = {
	[CI_PERMITTED]		= "permittied",
	[CI_DONTTOUCH]		= "dont-touch",
	[CI_CLSYNC]		= "clsync",
	[CI_EMPTY]		= "empty",
};

#endif

static char *const socketauth[] = {
	[SOCKAUTH_UNSET]	= "",
	[SOCKAUTH_NULL]		= "null",
//	[SOCKAUTH_PAM]		= "pam",
	NULL
};

static char *const threading_modes[] = {
	[PM_OFF]		= "off",
	[PM_SAFE]		= "safe",
	[PM_FULL]		= "full",
	NULL
};

#ifdef CAPABILITIES_SUPPORT
static char *const splitting_modes[] = {
	[SM_OFF]		= "off",
	[SM_THREAD]		= "thread",
	[SM_PROCESS]		= "process",
	NULL
};
#endif

static char *const notify_engines[] = {
	[NE_UNDEFINED]		= "",
	[NE_INOTIFY]		= "inotify",
	[NE_KQUEUE]		= "kqueue",
	[NE_FANOTIFY]		= "fanotify",
	[NE_BSM]		= "bsm",
	[NE_BSM_PREFETCH]	= "bsm_prefetch",
	[NE_DTRACEPIPE]		= "dtracepipe",
	[NE_GIO]		= "gio",
	NULL
};

static char *const output_methods[] = {
	[OM_STDERR]		= "stderr",
	[OM_STDOUT]		= "stdout",
	[OM_SYSLOG]		= "syslog",
	NULL
};

static char *const modes[] = {
	[MODE_UNSET]		= "",
	[MODE_SIMPLE]		= "simple",
	[MODE_DIRECT]		= "direct",
	[MODE_SHELL]		= "shell",
	[MODE_RSYNCSHELL]	= "rsyncshell",
	[MODE_RSYNCDIRECT]	= "rsyncdirect",
	[MODE_RSYNCSO]		= "rsyncso",
	[MODE_SO]		= "so",
	NULL
};

static char *const status_descr[] = {
	[STATE_EXIT]		= "exiting",
	[STATE_STARTING]	= "starting",
	[STATE_RUNNING]		= "running",
	[STATE_SYNCHANDLER_ERR]	= "synchandler error",
	[STATE_REHASH]		= "rehashing",
	[STATE_TERM]		= "terminating",
	[STATE_THREAD_GC]	= "thread gc",
	[STATE_INITSYNC]	= "initsync",
	[STATE_HOLDON]		= "hold on",
	NULL
};

int syntax() {
	info("possible options:");
	int i=-1;
	while (long_options[++i].name != NULL) {
		switch (long_options[i].val) {
			case SYNCHANDLERARGS0:
			case SYNCHANDLERARGS1:
				continue;
		}
		if (long_options[i].val & OPTION_CONFIGONLY)
			continue;

		info("\t--%-24s%c%c%s", long_options[i].name, 
				long_options[i].val & OPTION_LONGOPTONLY ? ' ' : '-', 
				long_options[i].val & OPTION_LONGOPTONLY ? ' ' : long_options[i].val, 
			(long_options[i].has_arg == required_argument ? " argument" : ""));
	}
	exit(EINVAL);
}

int ncpus;
pid_t parent_pid;

pid_t waitpid_timed(pid_t child_pid, int *status_p, long sec, long nsec) {
	struct timespec ts;
	int status;

	ts.tv_sec  = sec;
	ts.tv_nsec = nsec;

	while (ts.tv_sec >= 0) {
		if (waitpid(child_pid, &status, WNOHANG)<0) {
			if (errno==ECHILD)
				return child_pid;
			return -1;
		} else
			if (status_p != NULL)
				*status_p = status;

		ts.tv_nsec -= WAITPID_TIMED_GRANULARITY;
		if (ts.tv_nsec < 0) {
			ts.tv_nsec += 1000*1000*1000;
			ts.tv_sec--;
		}
	}

	return 0;
}

int parent_isalive() {
	int rc;
	debug(12, "parent_pid == %u", parent_pid);

	if ((rc=kill(parent_pid, 0))) {
		if (errno == ESRCH) {
			debug(1, "kill(%u, 0) => %i; errno => %s", parent_pid, rc, strerror(errno));
			return 0;
		}
	}

	return 1;
}

void child_sigchld() {
	if (getppid() != 1)
		return;

	debug(1, "Got SIGCHLD (parent ended). Exit.");
	exit(-1);
	return;
}

int sethandler_sigchld(void (*handler)()) {
	struct sigaction sa;
	sa.sa_handler = handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
	critical_on (sigaction(SIGCHLD, &sa, 0) == -1);

	return 0;
}

# ifndef __linux__
void *watchforparent(void *parent_pid_p) {
	while (1) {
		child_sigchld();
		sleep(SLEEP_SECONDS);
	}

	return NULL;
}
# endif

pthread_t pthread_watchforparent;
pid_t fork_helper() {
	pid_t pid = fork();

	if (!pid) {	// is child?

		parent_pid = getppid();

		// Anti-zombie:

# ifdef __linux__
		// Linux have support of "prctl(PR_SET_PDEATHSIG, signal);"
		sethandler_sigchld(child_sigchld);
		prctl(PR_SET_PDEATHSIG, SIGCHLD);
# else
		pthread_create(&pthread_watchforparent, NULL, watchforparent, &parent_pid);
# endif
		debug(20, "parent_pid == %u", parent_pid);
		return 0;
	}

	return pid;
}

int version() {
	info(PROGRAM" v%i.%i.%i"REVISION"\n\t"AUTHOR"\n\nCompiled with options"
#ifdef _DEBUG_SUPPORT
		" -D_DEBUG_SUPPORT"
#endif
#ifdef _DEBUG_FORCE
		" -D_DEBUG_FORCE"
#endif
#ifdef KQUEUE_SUPPORT
		" -DKQUEUE_SUPPORT"
#endif
#ifdef INOTIFY_SUPPORT
		" -DINOTIFY_SUPPORT"
#endif
#ifdef INOTIFY_OLD
		" -DINOTIFY_OLD"
#endif
#ifdef FANOTIFY_SUPPORT
		" -DFANOTIFY_SUPPORT"
#endif
#ifdef BSM_SUPPORT
		" -DBSM_SUPPORT"
#endif
#ifdef GIO_SUPPORT
		" -DGIO_SUPPORT"
#endif
#ifdef DTRACEPIPE_SUPPORT
		" -DDTRACEPIPE_SUPPORT"
#endif
#ifdef BACKTRACE_SUPPORT
		" -DBACKTRACE_SUPPORT"
#endif
#ifdef CAPABILITIES_SUPPORT
		" -DCAPABILITIES_SUPPORT"
#endif
#ifdef SECCOMP_SUPPORT
		" -DSECCOMP_SUPPORT"
#endif
#ifdef GETMNTENT_SUPPORT
		" -DGETMNTENT_SUPPORT"
#endif
#ifdef UNSHARE_SUPPORT
		" -DUNSHARE_SUPPORT"
#endif
#ifdef PIVOTROOT_OPT_SUPPORT
		" -DPIVOTROOT_OPT_SUPPORT"
#endif
#ifdef CGROUP_SUPPORT
		" -DCGROUP_SUPPORT"
#endif
#ifdef TRE_SUPPORT
		" -DTRE_SUPPORT"
#endif
#ifdef HL_LOCKS
		" -DHL_LOCKS"
#endif
		, VERSION_MAJ, VERSION_MID, VERSION_MIN);
	exit(0);
}

int clsyncapi_getapiversion() {
	return CLSYNC_API_VERSION;
}

/**
 * @brief 			Gets raw (string) an option value by an option name
 * 
 * @param[in]	_ctx_p		Context
 @ @param[in]	variable_name	The name of the option
 * 
 * @retval	char *		Pointer to newly allocated string, if successful
 * @retval	NULL		On error
 * 
 */
const char *parameter_get(const char *variable_name, void *_ctx_p) {
	const ctx_t *ctx_p = _ctx_p;
	const struct option *long_option_p = long_options;
	int param_id = -1;
	debug(8, "(\"%s\", %p)", variable_name, ctx_p);

	while (long_option_p->name != NULL) {
		if (!strcmp(long_option_p->name, variable_name)) {
			param_id = long_option_p->val;
			break;
		}

		long_option_p++;
	}

	if (param_id == -1) {
		errno = ENOENT;
		return NULL;
	}

	debug(9, "ctx_p->flags_values_raw[%i] == \"%s\"", param_id, ctx_p->flags_values_raw[param_id]);

	return ctx_p->flags_values_raw[param_id];
}

/**
 * @brief 			Gets raw (string) an option value by an option name and
 * 				updates ctx_p->synchandler_argf
 * 
 * @param[in]	_ctx_p		Context
 @ @param[in]	variable_name	The name of the option
 * 
 * @retval	char *		Pointer to newly allocated string, if successful
 * @retval	NULL		On error
 * 
 */
const char *parameter_get_wmacro(const char *variable_name, void *_ctx_p) {
	ctx_t *ctx_p = _ctx_p;
	static struct dosync_arg dosync_arg = {0};
	debug(9, "(\"%s\", %p)", variable_name, _ctx_p);

	if (*variable_name < 'A' || *variable_name > 'Z')
		return parameter_get(variable_name, _ctx_p);

	if (!strcmp(variable_name, "RSYNC-ARGS")) {
		ctx_p->synchandler_argf |= SHFL_RSYNC_ARGS;
		return NULL;
	}
	if (!strcmp(variable_name, "INCLUDE-LIST")) {
		ctx_p->synchandler_argf |= SHFL_INCLUDE_LIST;
		return NULL;
	}

	const char *r = sync_parameter_get(variable_name, &dosync_arg);

	if (r == dosync_arg.outf_path) {
		ctx_p->synchandler_argf |= SHFL_INCLUDE_LIST_PATH;
		return NULL;
	}

	if (r == dosync_arg.excf_path) {
		ctx_p->synchandler_argf |= SHFL_EXCLUDE_LIST_PATH;
		return NULL;
	}

	errno = ENOENT;
	return NULL;
}

/**
 * @brief 			Expands option values, e. g. "/var/log/clsync-%label%.pid" -> "/var/log/clsync-clone.pid"
 * 
 * @param[in]	ctx_p		Context
 * @param[in]	arg		An allocated string with unexpanded value. Will be free'd
 * @param[out]	macro_count_p	A pointer to count of found macro-s
 * @param[out]	expand_count_p	A pointer to count of expanded macro-s
 * @param[in]	parameter_get	A function to resolve macro-s
 * @param[in]	parameter_get_arg An argument to the function
 * 
 * @retval	char *		Pointer to newly allocated string, if successful
 * @retval	NULL		On error
 * 
 */
char *parameter_expand(
		ctx_t *ctx_p,
		char *arg,
		int exceptionflags,
		int *macro_count_p,
		int *expand_count_p,
		const char *(*parameter_get)(const char *variable_name, void *arg),
		void *parameter_get_arg
) {
	debug(9, "(ctx_p, \"%s\" [%p], ...)", arg, arg);
	char *ret = NULL;
	size_t ret_size = 0, ret_len = 0;

#ifdef PARANOID
	if (arg == NULL) {
		errno = EINVAL;
		return NULL;
	}
#endif

	if (macro_count_p != NULL)
		*macro_count_p  = 0;
	if (expand_count_p != NULL)
		*expand_count_p = 0;

	char *ptr = &arg[-1];
	while (1) {
		ptr++;

		switch (*ptr) {
			case 0:
				if (ret == NULL) {
					debug(3, "Expanding value \"%s\" to \"%s\" (case #1)", arg, arg);
					return arg;
				}
				ret[ret_len] = 0;
				debug(3, "Expanding value \"%s\" to \"%s\" (case #0)", arg, ret);
				free(arg);
				return ret;
			case '%': {
				if (ptr[1] == '%') {
					ret[ret_len++] = *(ptr++);
					break;
				}

				debug(25, "A macro");
				char nest_searching = 1;
				char *ptr_nest = ptr;
				while (nest_searching) {
					ptr_nest++;

					switch (*ptr_nest) {
						case 0:
							ret[ret_len] = 0;
							if (!(exceptionflags&1))
								warning("Unexpected end of macro-substitution \"%s\" in value \"%s\"; result value is \"%s\"", ptr, arg, ret);
							free(arg);
							return ret;
						case '%': {
							char       *variable_name;
							const char *variable_value;
							size_t      variable_value_len;

							if (macro_count_p != NULL)
								(*macro_count_p)++;

							nest_searching = 0;
							*ptr_nest = 0;
							variable_name  = &ptr[1];
							debug(15, "The macro is \"%s\"", variable_name);
							if (!strcmp(variable_name, "PID")) {
								debug(35, "\"PID\"", variable_name);
								if (!*ctx_p->pid_str) {
									snprintf(ctx_p->pid_str, 64, "%u", ctx_p->pid);
									ctx_p->pid_str_len = strlen(ctx_p->pid_str);
								}
								variable_value     = ctx_p->pid_str;
								variable_value_len = ctx_p->pid_str_len;
							} else
							if (*variable_name >= 'A' && *variable_name <= 'Z' && (exceptionflags&4)) {	// Lazy substitution, preserving the value
								debug(35, "Lazy substitution", variable_name);
								variable_value     =  ptr;
								variable_value_len = (ptr_nest - ptr + 1);
								parameter_get(variable_name, parameter_get_arg);
							} else {									// Substituting
								debug(35, "Substitution", variable_name);
								errno = 0;
								variable_value = parameter_get(variable_name, parameter_get_arg);
								if (variable_value == NULL) {
									if (!(exceptionflags&2) && (errno != ENOENT))
										warning("Variable \"%s\" is not set (%s)", variable_name, strerror(errno));
									*ptr_nest = '%';
									errno = 0;
									break;
								}
								variable_value_len = strlen(variable_value);

								if (expand_count_p != NULL)
									(*expand_count_p)++;
							}
							*ptr_nest = '%';
							if (ret_len+variable_value_len+1 >= ret_size) {
								ret_size = ret_len+variable_value_len+1 + ALLOC_PORTION;
								ret      = xrealloc(ret, ret_size);
							}
							memcpy(&ret[ret_len], variable_value, variable_value_len);
							ret_len += variable_value_len;
							break;
						}
					}
				}
				ptr = ptr_nest;
				break;
			}
			default: {
				if (ret_len+2 >= ret_size) {
					ret_size += ALLOC_PORTION+2;
					ret       = xrealloc(ret, ret_size);
				}
				ret[ret_len++] = *ptr;
				break;
			}
		}
	}
	error("Unknown internal error");
	return arg;
}

static inline int synchandler_arg(char *arg, size_t arg_len, void *_ctx_p, enum shargsid shargsid) {
	ctx_t *ctx_p = _ctx_p;
	debug(9, "(\"%s\" [%p], %u, %p, %u)", arg, arg, arg_len, _ctx_p, shargsid);

	if (!strcmp(arg, "%RSYNC-ARGS%")) {
		char *args_e[] = RSYNC_ARGS_E, *args_i[] = RSYNC_ARGS_I, **args_p;
		free(arg);

		args_p = ctx_p->flags[RSYNCPREFERINCLUDE] ? args_i : args_e;

		while (*args_p != NULL) {
#ifdef VERYPARANOID
			if (!strcmp(*args_p, "%RSYNC-ARGS%")) {
				errno = EINVAL;
				critical("Infinite recursion detected");
			}
#endif
			if (synchandler_arg(strdup(*args_p), strlen(*args_p), ctx_p, shargsid))
				return errno;
			args_p++;
		}
		return 0;
	}

	if (ctx_p->synchandler_args[shargsid].c >= MAXARGUMENTS-2) {
		errno = E2BIG;
		error("There're too many sync-handler arguments "
			"(%u > "XTOSTR(MAXARGUMENTS-2)"; arg == \"%s\").",
			arg);
		return errno;
	}

#ifdef _DEBUG_FORCE
	debug(14, "ctx_p->synchandler_args[%u].v[%u] = %p", shargsid, ctx_p->synchandler_args[shargsid].c, arg);
#endif
	ctx_p->synchandler_args[shargsid].v[ctx_p->synchandler_args[shargsid].c++] = arg;

	return 0;
}

static int synchandler_arg0(char *arg, size_t arg_len, void *_ctx_p) {
	return synchandler_arg(arg, arg_len, _ctx_p, SHARGS_PRIMARY);
}

static int synchandler_arg1(char *arg, size_t arg_len, void *_ctx_p) {
	return synchandler_arg(arg, arg_len, _ctx_p, SHARGS_INITIAL);
}

/* strtol wrapper with error checks */
static inline long xstrtol(const char *str, int *err) {
	long res;
	char *endptr;

	errno = 0;
	res = strtol(str, &endptr, 0);
	if (errno || *endptr) {
		error("argument \"%s\" can't be parsed as a number", str);
		*err = EINVAL;
	}
	return res;
}

static inline int parse_customsignals(ctx_t *ctx_p, char *arg) {
	char *ptr = arg, *start = arg;
	int ret = 0;
	unsigned int signal;
	do {
		switch (*ptr) {
			case 0:
			case ',':
			case ':':
				// TODO: use xstrtol() instead of atoi()

				//signal = (unsigned int)xstrtol(start, &ret);
				signal = (unsigned int)atoi(start);
				if (ret) {
					errno = ret;
					return errno;
				}
				if (signal == 0) {
					// flushing the setting
					int i = 0;
					while (i < 256) {
						if (ctx_p->customsignal[i]) {
							free(ctx_p->customsignal[i]);
							ctx_p->customsignal[i] = NULL;
						}
						i++;
					}
#ifdef _DEBUG_FORCE
					fprintf(stderr, "Force-Debug: parse_parameter(): Reset custom signals.\n");
#endif
				} else {
					if (*ptr != ':') {
						char ch = *ptr;

						*ptr = 0;
							errno = EINVAL;
							error("Expected \":\" in \"%s\"", start);
						*ptr = ch;
						return errno;
					}

					{
						char ch, *end;
						ptr++;

						end = ptr;
						while (*end && *end != ',') end++;

						if (end == ptr) {
							errno = EINVAL;
							error("Empty config block name on signal \"%u\"", signal);
							return errno;
						}

						if (signal > MAXSIGNALNUM) {
							errno = EINVAL;
							error("Too high value of the signal: \"%u\" > "XTOSTR(MAXSIGNALNUM)"", signal);
							return errno;
						}

						ch = *end; *end = 0;
						ctx_p->customsignal[signal] = strdup(ptr);
						*end = ch;
#ifdef _DEBUG_FORCE
						fprintf(stderr, "Force-Debug: parse_parameter(): Adding custom signal %u.\n", signal);
#endif
						ptr = end;
					}
				}
				start = ptr+1;
				break;
			case '0' ... '9':
				break;
			default:
				errno = EINVAL;
				error("Expected a digit, comma (or colon) but got \"%c\"", *ptr);
				return errno;
		}
	} while (*(ptr++));

	return 0;
}

static int parse_parameter(ctx_t *ctx_p, uint16_t param_id, char *arg, paramsource_t paramsource) {
	int ret = 0;
#ifdef _DEBUG_FORCE
	fprintf(stderr, "Force-Debug: parse_parameter(): %i: %i = \"%s\"\n", paramsource, param_id, arg);
#endif
	switch (paramsource) {
		case PS_CONTROL:
		case PS_ARGUMENT:
			if (param_id & OPTION_CONFIGONLY) {
				syntax();
				return 0;
			}
			ctx_p->flags_set[param_id] = 1;
			break;
		case PS_CONFIG:
			if (ctx_p->flags_set[param_id])
				return 0;
			ctx_p->flags_set[param_id] = 1;
			break;
		case PS_DEFAULTS:
#ifdef VERYPARANOID
			if (ctx_p->flags_set[param_id]) {
				error("Parameter #%i is already set. No need in setting the default value.", param_id);
				return 0;
			}
#endif
			break;
/*		case PS_REHASH:
			arg = ctx_p->flags_values_raw[param_id];
#ifdef VERYPARANOID
			critical_on (arg == NULL);
#endif

			debug(9, "Rehash setting %i -> \"%s\"", param_id, arg);
			break;*/
		case PS_CORRECTION:
			critical_on (arg == NULL);
			debug(9, "Correcting setting %i -> \"%s\"", param_id, arg);
			break;
		default:
			error("Unknown parameter #%i source (value \"%s\").", param_id, arg!=NULL ? arg : "");
			break;
	}

	if ((arg != NULL) /*&& (paramsource != PS_REHASH)*/) {
		if (param_id != SYNCHANDLERARGS0 && param_id != SYNCHANDLERARGS1)
			arg = parameter_expand(ctx_p, arg, 0, NULL, NULL, parameter_get, ctx_p);

		if (ctx_p->flags_values_raw[param_id] != NULL)
			free(ctx_p->flags_values_raw[param_id]);
		ctx_p->flags_values_raw[param_id] = arg;
	}

	switch (param_id) {
		case '?':
		case HELP:
			syntax();
			break;
		case CONFIGFILE:
			ctx_p->config_path    = *arg ? arg : NULL;
			break;
		case CONFIGBLOCK:
			ctx_p->config_block   = *arg ? arg : NULL;
			break;
		case CONFIGBLOCKINHERITS:
			break;
		case CUSTOMSIGNALS:
			if (paramsource == PS_CONTROL) {
				warning("Cannot change \"custom-signal\" in run-time. Ignoring.");
				return 0;
			}

			if (parse_customsignals(ctx_p, arg))
				return errno;
			break;
		case UID: {
			struct passwd *pwd = getpwnam(arg);
			ctx_p->flags[param_id]++;

			if (pwd == NULL) {
				ctx_p->uid = (unsigned int)xstrtol(arg, &ret);
				break;
			}

			ctx_p->uid = pwd->pw_uid;
			break;
		}
		case GID: {
			struct group *grp = getgrnam(arg);
			ctx_p->flags[param_id]++;

			if (grp == NULL) {
				ctx_p->gid = (unsigned int)xstrtol(arg, &ret);
				break;
			}

			ctx_p->gid = grp->gr_gid;
			break;
		}
#ifdef CAPABILITIES_SUPPORT
# ifdef SECCOMP_SUPPORT
		case SECURESPLITTING: {
			if (ctx_p->flags_values_raw[CHECK_EXECVP_ARGS] == NULL)
				ctx_p->flags[CHECK_EXECVP_ARGS]++;

			if (ctx_p->flags_values_raw[SECCOMP_FILTER] == NULL)
				ctx_p->flags[SECCOMP_FILTER]++;

			if (ctx_p->flags_values_raw[FORBIDDEVICES] == NULL)
				ctx_p->flags[FORBIDDEVICES]++;

			if (ctx_p->flags_values_raw[SPLITTING] != NULL)
				break;

			arg = "process";
		}
		case SPLITTING: {
			char *value, *arg_orig = arg;

			if (!*arg) {
				ctx_p->flags[param_id] = 0;
				return 0;
			}

			splittingmode_t splittingmode = getsubopt(&arg, splitting_modes, &value);
			if((int)splittingmode == -1) {
				errno = EINVAL;
				error("Invalid splitting mode entered: \"%s\"", arg_orig);
				return EINVAL;
			}
			ctx_p->flags[SPLITTING] = splittingmode;

			if (param_id != SECURESPLITTING)
				break;
			switch (splittingmode) {
				case SM_THREAD:
					ctx_p->flags[FORGET_PRIVTHREAD_INFO]++;
					break;
				case SM_PROCESS:
					break;
				case SM_OFF:
					errno = EINVAL;
					error("Cannot understand \"--secure-splitting=off\". This configuration line have no sence.");
					break;
			}
			if (ctx_p->flags_values_raw[PERMIT_MPROTECT] == NULL)
				ctx_p->flags[PERMIT_MPROTECT] = (splittingmode != SM_THREAD);
			break;
		}
# endif
		case PRIVILEGEDUID: {
			struct passwd *pwd = getpwnam(arg);
			ctx_p->flags[param_id]++;

			if (pwd == NULL) {
				ctx_p->privileged_uid = (unsigned int)xstrtol(arg, &ret);
				break;
			}

			ctx_p->privileged_uid = pwd->pw_uid;
			break;
		}
		case PRIVILEGEDGID: {
			struct group *grp = getgrnam(arg);
			ctx_p->flags[param_id]++;

			if (grp == NULL) {
				ctx_p->privileged_gid = (unsigned int)xstrtol(arg, &ret);
				break;
			}

			ctx_p->privileged_gid = grp->gr_gid;
			break;
		}
		case SYNCHANDLERUID: {
			struct passwd *pwd = getpwnam(arg);
			ctx_p->flags[param_id]++;

			if (pwd == NULL) {
				ctx_p->synchandler_uid = (unsigned int)xstrtol(arg, &ret);
				break;
			}

			ctx_p->synchandler_uid = pwd->pw_uid;
			break;
		}
		case SYNCHANDLERGID: {
			struct group *grp = getgrnam(arg);
			ctx_p->flags[param_id]++;

			if (grp == NULL) {
				ctx_p->synchandler_gid = (unsigned int)xstrtol(arg, &ret);
				break;
			}

			ctx_p->synchandler_gid = grp->gr_gid;
			break;
		}
		case CAP_PRESERVE: {
			char *subopts = arg;

			ctx_p->caps  = 0;

			while (*subopts != 0) {
				char *value;
				__u32 cap = getsubopt(&subopts, capabilities, &value);
				debug(4, "cap == 0x%x", cap);
				if (cap != X_CAP_RESET)
					ctx_p->caps |= CAP_TO_MASK(XCAP_TO_CAP(cap));
			}

			break;
		}
		case CAPS_INHERIT: {
			char *value, *arg_orig = arg;

			if (!*arg) {
				ctx_p->flags[param_id] = 0;
				return 0;
			}

			capsinherit_t capsinherit = getsubopt(&arg, capsinherits, &value);
			if((int)capsinherit == -1) {
				errno = EINVAL;
				error("Invalid capabilities inheriting mode entered: \"%s\"", arg_orig);
				return EINVAL;
			}
			ctx_p->flags[CAPS_INHERIT] = capsinherit;

			break;
		}
#endif
		case CHROOT:
			if (paramsource == PS_CONTROL) {
				warning("Cannot change \"chroot\" in run-time. Ignoring.");
				return 0;
			}
			if (!*arg) {
				free(ctx_p->chroot_dir);
				ctx_p->chroot_dir = NULL;
				return 0;
			}

			ctx_p->chroot_dir	= arg;
			break;
#ifdef PIVOTROOT_OPT_SUPPORT
		case PIVOT_ROOT: {
			char *value, *arg_orig = arg;

			if (!*arg) {
				ctx_p->flags[PIVOT_ROOT] = DEFAULT_PIVOT_MODE;
				return 0;
			}

			pivotroot_way_t pivotway = getsubopt(&arg, pivotrootways, &value);
			if((int)pivotway == -1) {
				errno = EINVAL;
				error("Invalid pivot_root use way entered: \"%s\"", arg_orig);
				return EINVAL;
			}
			ctx_p->flags[PIVOT_ROOT] = pivotway;

			break;
		}
#endif
#ifdef UNSHARE_SUPPORT
		case DETACH_NETWORK: {
			char *value, *arg_orig = arg;

			if (!*arg) {
				ctx_p->flags[param_id] = 0;
				return 0;
			}

			detachnetwork_way_t detachnetwork_way = getsubopt(&arg, detachnetworkways, &value);
			if((int)detachnetwork_way == -1) {
				errno = EINVAL;
				error("Invalid network detach way entered: \"%s\"", arg_orig);
				return EINVAL;
			}
			ctx_p->flags[DETACH_NETWORK] = detachnetwork_way;

			break;
		}
#endif
#ifdef CAPABILITIES_SUPPORT
		case ADDPERMITTEDHOOKFILES: {
			char *ptr;
			if (paramsource == PS_CONTROL) {
				warning("Cannot change \"add-permitted-hook-files\" in run-time. Ignoring.");
				return 0;
			}

			while (ctx_p->permitted_hookfiles)
				free(ctx_p->permitted_hookfile[--ctx_p->permitted_hookfiles]);

			ptr = arg;
			while (1) {
				char *end = strchr(ptr, ',');

				if (end != NULL)
					*end =  0;

				if (!*ptr) {
					while (ctx_p->permitted_hookfiles)
						free(ctx_p->permitted_hookfile[--ctx_p->permitted_hookfiles]);

					if (end != NULL)
						ptr = &end[1];
					continue;
				}

				if (ctx_p->permitted_hookfiles >= MAXPERMITTEDHOOKFILES) {
					errno = EINVAL;
					error("Too many permitted hook files");
					return errno;
				}

				ctx_p->permitted_hookfile[ctx_p->permitted_hookfiles++] = strdup(ptr);

				if (end == NULL)
					break;

				*end = ',';
				ptr = &end[1];
			}
			break;
		}
#endif
#ifdef GETMNTENT_SUPPORT
		case MOUNTPOINTS: {
			char *ptr;
			if (paramsource == PS_CONTROL) {
				warning("Cannot change \"mountpoints\" in run-time. Ignoring.");
				return 0;
			}

			while (ctx_p->mountpoints)
				free(ctx_p->mountpoint[--ctx_p->mountpoints]);

			if (!*arg)
				break;

			ptr = arg;
			while (1) {
				char *end = strchr(ptr, ',');

				if (end != NULL)
					*end =  0;

				if (!*ptr) {
					while (ctx_p->mountpoints)
						free(ctx_p->mountpoint[--ctx_p->mountpoints]);

					if (end != NULL)
						ptr = &end[1];
					continue;
				}

				if (ctx_p->mountpoints >= MAXMOUNTPOINTS) {
					errno = EINVAL;
					error("Too many mountpoints");
					return errno;
				}

				ctx_p->mountpoint[ctx_p->mountpoints++] = strdup(ptr);

				if (end == NULL)
					break;

				*end = ',';
				ptr = &end[1];
			}
			break;
		}
#endif
		case PIDFILE:
			if (paramsource == PS_CONTROL) {
				warning("Cannot change \"pid-file\" in run-time. Ignoring.");
				return 0;
			}
			ctx_p->pidfile		= arg;
			break;
		case RETRIES:
			ctx_p->retries		= (unsigned int)xstrtol(arg, &ret);
			break;
		case THREADING: {
			char *value, *arg_orig = arg;

			if (!*arg) {
				ctx_p->flags[param_id] = 0;
				return 0;
			}

			threadingmode_t threadingmode = getsubopt(&arg, threading_modes, &value);
			if((int)threadingmode == -1) {
				errno = EINVAL;
				error("Invalid threading mode entered: \"%s\"", arg_orig);
				return EINVAL;
			}
			ctx_p->flags[THREADING] = threadingmode;

			break;
		}
		case OUTPUT_METHOD: {
			char *value, *arg_orig = arg;

			if (!*arg) {
				ctx_p->flags[param_id] = 0;
				return 0;
			}

			outputmethod_t outputmethod = getsubopt(&arg, output_methods, &value);
			if((int)outputmethod == -1) {
				errno = EINVAL;
				error("Invalid log writing destination entered: \"%s\"", arg_orig);
				return EINVAL;
			}
			ctx_p->flags[OUTPUT_METHOD] = outputmethod;

			break;
		}
#ifdef CLUSTER_SUPPORT
		case CLUSTERIFACE:
			ctx_p->cluster_iface		= arg;
			break;
		case CLUSTERMCASTIPADDR:
			ctx_p->cluster_mcastipaddr	= arg;
			break;
		case CLUSTERMCASTIPPORT:
			ctx_p->cluster_mcastipport	= (uint16_t)xstrtol(arg, &ret);
			break;
		case CLUSTERTIMEOUT:
			ctx_p->cluster_timeout		= (unsigned int)xstrtol(arg, &ret);
			break;
		case CLUSTERNODENAME:
			ctx_p->cluster_nodename		= arg;
			break;
		case CLUSTERHDLMIN:
			ctx_p->cluster_hash_dl_min	= (uint16_t)xstrtol(arg, &ret);
			break;
		case CLUSTERHDLMAX:
			ctx_p->cluster_hash_dl_max	= (uint16_t)xstrtol(arg, &ret);
			break;
		case CLUSTERSDLMAX:
			ctx_p->cluster_scan_dl_max	= (uint16_t)xstrtol(arg, &ret);
			break;
#endif
		case OUTLISTSDIR:
			ctx_p->listoutdir		= arg;
			break;
		case LABEL:
			ctx_p->label			= arg;
			break;
#ifdef CGROUP_SUPPORT
		case CG_GROUPNAME:
			ctx_p->cg_groupname		= arg;
			break;
#endif
		case STANDBYFILE:
			if(strlen(arg)) {
				ctx_p->standbyfile		= arg;
				ctx_p->flags[STANDBYFILE]	= 1;
			} else {
				ctx_p->standbyfile		= NULL;
				ctx_p->flags[STANDBYFILE]	= 0;
			}
			break;
		case MODSIGN: {
			char *subopts = arg;

			ctx_p->flags[MODSIGN] = 0;

			while (*subopts != 0) {
				char *value;
				typeof(ctx_p->flags[MODSIGN]) field = getsubopt(&subopts, stat_fields, &value);
				debug(4, "field == %i -> %x (%s)", field, xstatfield_to_statfield[field], value);
				if (field != X_STAT_FIELD_RESET)
					ctx_p->flags[MODSIGN] |= xstatfield_to_statfield[field];
			}

			debug(5, "ctx_p->flags[MODSIGN] == 0x%x", ctx_p->flags[MODSIGN]);
			break;
		}
		case SYNCDELAY: 
			ctx_p->syncdelay		= (unsigned int)xstrtol(arg, &ret);
			break;
		case DELAY:
			ctx_p->_queues[QUEUE_NORMAL].collectdelay  = (unsigned int)xstrtol(arg, &ret);
			break;
		case BFILEDELAY:
			ctx_p->_queues[QUEUE_BIGFILE].collectdelay = (unsigned int)xstrtol(arg, &ret);
			break;
		case BFILETHRESHOLD:
			ctx_p->bfilethreshold = (unsigned long)xstrtol(arg, &ret);
			break;
		case CANCEL_SYSCALLS: {
			char *subopts = arg;

			while (*subopts != 0) {
				char *value;
				typeof(ctx_p->flags[CANCEL_SYSCALLS]) syscall_bitmask = getsubopt(&subopts, syscalls_bitmask, &value);
				debug(4, "cancel syscall == %i -> 0x%x", syscall_bitmask, xcsc_to_csc[syscall_bitmask]);
				if (syscall_bitmask == X_CSC_RESET) {
					ctx_p->flags[CANCEL_SYSCALLS] = 0;
					continue;
				}

				ctx_p->flags[CANCEL_SYSCALLS] |= xcsc_to_csc[syscall_bitmask];
			}

			break;
		}
		case MONITOR: {
			char *value, *arg_orig = arg;
			if (paramsource == PS_CONTROL) {
				warning("Cannot change \"monitor\" in run-time. Ignoring.");
				return 0;
			}

			if (!*arg) {
				ctx_p->flags_set[param_id] = 0;
				return 0;
			}

			notifyengine_t notifyengine = getsubopt(&arg, notify_engines, &value);
			if((int)notifyengine == -1) {
				errno = EINVAL;
				error("Invalid FS monitor subsystem entered: \"%s\"", arg_orig);
				return EINVAL;
			}

			switch (notifyengine) {
#ifdef FANOTIFY_SUPPORT
				case NE_FANOTIFY:
#endif
#ifdef INOTIFY_SUPPORT
				case NE_INOTIFY:
#endif
#ifdef KQUEUE_SUPPORT
				case NE_KQUEUE:
#endif
#ifdef BSM_SUPPORT
				case NE_BSM:
				case NE_BSM_PREFETCH:
#endif
#ifdef GIO_SUPPORT
				case NE_GIO:
#endif
#ifdef DTRACEPIPE_SUPPORT
				case NE_DTRACEPIPE:
#endif
					break;
				default:
					error(PROGRAM" is compiled without %s subsystem support. Recompile with option \"--with-%s\" if you're planning to use it.", arg_orig, arg_orig);
					return EINVAL;
			}

			ctx_p->flags[MONITOR] = notifyengine;

			break;
		}
		case RSYNCINCLIMIT:
			ctx_p->rsyncinclimit = (unsigned int)xstrtol(arg, &ret);
			break;
		case SYNCTIMEOUT:
			ctx_p->synctimeout   = (unsigned int)xstrtol(arg, &ret);
			break;
		case PREEXITHOOK:
			if (strlen(arg)) {
				ctx_p->preexithookfile		= arg;
				ctx_p->flags[PREEXITHOOK]	= 1;
			} else {
				ctx_p->preexithookfile		= NULL;
				ctx_p->flags[PREEXITHOOK]	= 0;
			}
			break;
		case EXITHOOK:
			if (strlen(arg)) {
				ctx_p->exithookfile		= arg;
				ctx_p->flags[EXITHOOK]		= 1;
			} else {
				ctx_p->exithookfile		= NULL;
				ctx_p->flags[EXITHOOK]		= 0;
			}
			break;
		case IGNOREEXITCODE: {
			char *ptr = arg, *start = arg;
			unsigned char exitcode;
			do {
				switch(*ptr) {
					case 0:
					case ',':
//						*ptr=0;
						exitcode = (unsigned char)atoi(start);
						if (exitcode == 0) {
							// flushing the setting
							int i = 0;
							while (i < 256)
								ctx_p->isignoredexitcode[i++] = 0;
#ifdef _DEBUG_FORCE
							fprintf(stderr, "Force-Debug: parse_parameter(): Reset ignored exitcodes.\n");
#endif
						} else {
							ctx_p->isignoredexitcode[exitcode] = 1;
#ifdef _DEBUG_FORCE
							fprintf(stderr, "Force-Debug: parse_parameter(): Adding ignored exitcode %u.\n", exitcode);
#endif
						}
						start = ptr+1;
						break;
					case '0' ... '9':
						break;
					default:
						errno = EINVAL;
						error("Expected a digit or comma but got \"%c\"", *ptr);
						return errno;
				}
			} while(*(ptr++));
			break;
		}
		case SHOW_VERSION:
			version();
			break;
		case WATCHDIR:
			if (paramsource == PS_CONTROL) {
				warning("Cannot change \"watch-dir\" in run-time. Ignoring.");
				return 0;
			}
			ctx_p->watchdir		= arg;
			break;
		case SYNCHANDLER:
			ctx_p->handlerfpath	= arg;
			break;
		case RULESFILE:
			ctx_p->rulfpath		= arg;
			break;
		case DESTDIR: {
			char *sep = strstr(arg, "://");

			if (ctx_p->destproto != NULL) {
				free(ctx_p->destproto);
				ctx_p->destproto = NULL;
			}

			ctx_p->destdir	 = arg;

			if (sep == NULL) {
				char *at_ptr = strchr(arg, '@');
				char *cl_ptr = strchr(arg, ':');
				if (at_ptr != NULL && cl_ptr != NULL && at_ptr < cl_ptr) {
					ctx_p->destproto = strdup("rsync+ssh");
					debug(5, "Destination proto is: %s (case #0)", ctx_p->destproto);
				}
				break;
			}

			{
				char *ptr = arg;
				while (ptr < sep) {
					if (*ptr<'a' || *ptr>'z')
						break;
					ptr++;
				}
				if (ptr == sep) {
					size_t len = (ptr-arg)+1;
					ctx_p->destproto = xmalloc(len+1);
					memcpy(ctx_p->destproto, arg, len);
					ctx_p->destproto[len] = 0;
				}
				debug(5, "Destination proto is: %s (case #1)", ctx_p->destproto);
			}

			break;
		}
		case SOCKETPATH:
			ctx_p->socketpath	= arg;
			break;
		case SOCKETAUTH: {
			char *value;

			ctx_p->flags[SOCKETAUTH] = getsubopt(&arg, socketauth, &value);
			if (ctx_p->flags[SOCKETAUTH] == -1) {
				error("Wrong socket auth mech entered: \"%s\"", arg);
				return EINVAL;
			}
		}
		case SOCKETMOD:
			if (!sscanf(arg, "%o", (unsigned int *)&ctx_p->socketmod)) {
				error("Non octal value passed to --socket-mod: \"%s\"", arg);
				return EINVAL;
			}
			ctx_p->flags[param_id]++;
			break;
		case SOCKETOWN: {
			char *colon = strchr(arg, ':');
			uid_t uid;
			gid_t gid;

			if (colon == NULL) {
				struct passwd *pwent = getpwnam(arg);

				if(pwent == NULL) {
					error("Cannot find username \"%s\" (case #0)", 
						arg);
					return EINVAL;
				}

				uid = pwent->pw_uid;
				gid = pwent->pw_gid;

			} else {

				char user[USER_LEN+2], group[GROUP_LEN+2];

				memcpy(user, arg, MIN(USER_LEN, colon-arg));
				user[colon-arg] = 0;

				strncpy(group, &colon[1], GROUP_LEN);

				errno=0;
				struct passwd *pwent = getpwnam(user);
				if(pwent == NULL) {
					error("Cannot find username \"%s\" (case #1)", 
						user);
					return EINVAL;
				}

				errno=0;
				struct group  *grent = getgrnam(group);
				if(grent == NULL) {
					error("Cannot find group \"%s\"", 
						group);
					return EINVAL;
				}
	
				uid = pwent->pw_uid;
				gid = grent->gr_gid;
			}

			ctx_p->socketuid = uid;
			ctx_p->socketgid = gid;
			ctx_p->flags[param_id]++;

			debug(2, "socket: uid == %u; gid == %u", uid, gid);

			break;
		}
		case STATUSFILE:
			ctx_p->statusfile	= arg;
			break;
		case DUMPDIR:
			ctx_p->dump_path	= arg;
			break;
		case MODE: {
			char *value;

			ctx_p->flags[MODE]  = getsubopt(&arg, modes, &value);
			if (ctx_p->flags[MODE] == -1) {
				error("Wrong mode name entered: \"%s\"", arg);
				return EINVAL;
			}
			break;
		}
		case SYNCHANDLERARGS0:
			str_splitargs(arg, synchandler_arg0, ctx_p);
			break;
		case SYNCHANDLERARGS1:
			str_splitargs(arg, synchandler_arg1, ctx_p);
			break;
		default:
			if (arg == NULL)
				ctx_p->flags[param_id]++;
			else
				ctx_p->flags[param_id] = xstrtol(arg, &ret);
#ifdef _DEBUG_FORCE
			fprintf(stderr, "Force-Debug: flag %i is set to %i\n", param_id&0xff, ctx_p->flags[param_id]);
#endif
			break;
	}
	return ret;
}

int arguments_parse(int argc, char *argv[], struct ctx *ctx_p) {
	int c;
	int option_index = 0;

	// Generating "optstring" (man 3 getopt_long) with using information from struct array "long_options"
	char *optstring     = alloca((('z'-'a'+1)*3 + '9'-'0'+1)*3 + 1);
	char *optstring_ptr = optstring;

	const struct option *lo_ptr = long_options;
	while (lo_ptr->name != NULL) {
		if (!(lo_ptr->val & (OPTION_CONFIGONLY|OPTION_LONGOPTONLY))) {
			*(optstring_ptr++) = lo_ptr->val & 0xff;

			if (lo_ptr->has_arg == required_argument)
				*(optstring_ptr++) = ':';

			if (lo_ptr->has_arg == optional_argument) {
				*(optstring_ptr++) = ':';
				*(optstring_ptr++) = ':';
			}
		}
		lo_ptr++;
	}
	*optstring_ptr = 0;
#ifdef _DEBUG_FORCE
	fprintf(stderr, "Force-Debug: %s\n", optstring);
#endif

	// Parsing arguments
	while (1) {
		c = getopt_long(argc, argv, optstring, long_options, &option_index);
	
		if (c == -1) break;
		int ret = parse_parameter(ctx_p, c, optarg == NULL ? NULL : strdup(optarg), PS_ARGUMENT);
		if (ret) return ret;
	}

	if (optind < argc) {
		synchandler_args_t *args_p = &ctx_p->synchandler_args[SHARGS_PRIMARY];

		while (args_p->c)
			free(args_p->v[--args_p->c]);

		if ((optind+1 != argc) || (*argv[optind])) {	// If there's only "" after the "--", just reset "synchandler_argc" to "0", otherwise:
			do {
				if (synchandler_arg0(strdup(argv[optind++]), 0, ctx_p))
					return errno;
			} while (optind < argc);
		}
	}

	return 0;
}

void gkf_parse(ctx_t *ctx_p, GKeyFile *gkf, paramsource_t paramsource) {
	debug(9, "");
	char *config_block = (char *)ctx_p->config_block;
	while (config_block != NULL) {
		const struct option *lo_ptr = long_options;

		if (config_block != ctx_p->config_block) {
			ctx_p->flags_values_raw[CONFIGBLOCKINHERITS] = NULL;
			ctx_p->flags_set[CONFIGBLOCKINHERITS] = 0;
		}
		while (lo_ptr->name != NULL) {
			gchar *value = g_key_file_get_value(gkf, config_block, lo_ptr->name, NULL);
			if(value != NULL) {
				int ret = parse_parameter(ctx_p, lo_ptr->val, value, paramsource);
				if(ret) exit(ret);
			}
			lo_ptr++;
		}

		if (config_block != ctx_p->config_block)
			free(config_block);

		config_block = ctx_p->flags_values_raw[CONFIGBLOCKINHERITS];

		if (config_block != NULL)
			debug(2, "Next block is: %s", config_block);
	};

	return;
}

int configs_parse(ctx_t *ctx_p, paramsource_t paramsource) {
	GKeyFile *gkf;

	gkf = g_key_file_new();

	if (ctx_p->config_path) {
		GError *g_error = NULL;

		if (!strcmp(ctx_p->config_path, "/NULL/")) {
			debug(2, "Empty path to config file. Don't read any of config files.");
			return 0;
		}

		debug(1, "Trying config-file \"%s\" (case #0)", ctx_p->config_path);
		if (!g_key_file_load_from_file(gkf, ctx_p->config_path, G_KEY_FILE_NONE, &g_error)) {
			error("Cannot open/parse file \"%s\" (g_error #%u.%u: %s)", ctx_p->config_path, g_error->domain, g_error->code, g_error->message);
			g_key_file_free(gkf);
			return -1;
		} else
			gkf_parse(ctx_p, gkf, paramsource);

	} else {
		char  *config_paths[] = CONFIG_PATHS;
		char **config_path_p = config_paths, *config_path_real = xmalloc(PATH_MAX);
		size_t config_path_real_size=PATH_MAX;

		char  *homedir     = getenv("HOME");
		size_t homedir_len = (homedir == NULL ? 0 :strlen(homedir));

		while (*config_path_p != NULL) {
			size_t config_path_len = strlen(*config_path_p);

			if (config_path_len+homedir_len+3 > config_path_real_size) {
				config_path_real_size = config_path_len+homedir_len+3;
				config_path_real      = xmalloc(config_path_real_size);
			}

			if ((*config_path_p[0] != '/') && (homedir_len >= 0)) {
				memcpy(config_path_real, homedir, homedir_len);
				config_path_real[homedir_len] = '/';
				memcpy(&config_path_real[homedir_len+1], *config_path_p, config_path_len+1);
			} else 
				memcpy(config_path_real, *config_path_p, config_path_len+1);

			debug(1, "Trying config-file \"%s\" (case #1)", config_path_real);
			if (!g_key_file_load_from_file(gkf, config_path_real, G_KEY_FILE_NONE, NULL)) {
				debug(1, "Cannot open/parse file \"%s\"", config_path_real);
				config_path_p++;
				continue;
			}

			gkf_parse(ctx_p, gkf, paramsource);

			break;
		}
		free(config_path_real);
	}

	g_key_file_free(gkf);

	return 0;
}

int ctx_check(ctx_t *ctx_p) {
	int ret = 0;
#ifdef CLUSTER_SUPPORT
	struct utsname utsname;
#endif

	if (ctx_p->socketpath != NULL) {
#ifndef ENABLE_SOCKET
		ret = EINVAL;
		error("clsync is compiled without control socket support, option \"--socket\" cannot be used.");
#endif
		if (ctx_p->flags[SOCKETAUTH] == SOCKAUTH_UNSET)
			ctx_p->flags[SOCKETAUTH] = SOCKAUTH_NULL;
	}

	if ((ctx_p->flags[SOCKETOWN]) && (ctx_p->socketpath == NULL)) {
		ret = errno = EINVAL;
		error("\"--socket-own\" is useless without \"--socket\"");
	}

	if ((ctx_p->flags[SOCKETMOD]) && (ctx_p->socketpath == NULL)) {
		ret = errno = EINVAL;
		error("\"--socket-mod\" is useless without \"--socket\"");
	}

	if ((ctx_p->flags[SOCKETAUTH]) && (ctx_p->socketpath == NULL)) {
		ret = errno = EINVAL;
		error("\"--socket-auth\" is useless without \"--socket\"");
	}

#ifdef PIVOTROOT_OPT_SUPPORT
	if ((ctx_p->flags[PIVOT_ROOT] != PW_OFF) && (ctx_p->chroot_dir == NULL)) {
		ret = errno = EINVAL;
		error("\"--pivot-root\" cannot be used without \"--chroot\"");
	}

	if ((ctx_p->flags[PIVOT_ROOT] != PW_OFF) && (ctx_p->mountpoints))
		warning("\"--mountpoints\" is set while \"--pivot-root\" is set, too");
#endif

#ifdef VERYPARANOID
	if ((ctx_p->retries != 1) && ctx_p->flags[THREADING]) {
		ret = errno = EINVAL;
		error("\"--retries\" values should be equal to \"1\" for this \"--threading\" value.");
	}
#endif

	if (ctx_p->flags[STANDBYFILE] && (ctx_p->flags[MODE] == MODE_SIMPLE)) {
		ret = errno = EINVAL;
		error("Sorry but option \"--standby-file\" cannot be used in mode \"simple\", yet.");
	}

	if (ctx_p->flags[THREADING] && ctx_p->flags[ONLYINITSYNC]) {
		ret = errno = EINVAL;
		error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--only-initialsync\".");
	}

	if (ctx_p->flags[THREADING] && ctx_p->flags[EXITONNOEVENTS]) {
		ret = errno = EINVAL;
		error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--exit-on-no-events\".");
	}
	if (ctx_p->flags[THREADING] && ctx_p->flags[MAXITERATIONS]) {
		ret = errno = EINVAL;
		error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--max-iterations\".");
	}
	if (ctx_p->flags[THREADING] && ctx_p->flags[PREEXITHOOK]) {
		ret = errno = EINVAL;
		error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--pre-exit-hook\".");
	}
	if (ctx_p->flags[THREADING] && ctx_p->flags[SPLITTING] == SM_THREAD) {
		ret = errno = EINVAL;
		error("Conflicting options: This value of \"--threading\" cannot be used in conjunction with \"--splitting=thread\".");
	}
	if (ctx_p->flags[SKIPINITSYNC] && ctx_p->flags[EXITONNOEVENTS]) {
		ret = errno = EINVAL;
		error("Conflicting options: \"--skip-initialsync\" and \"--exit-on-no-events\" cannot be used together.");
	}
	if (ctx_p->flags[ONLYINITSYNC] && ctx_p->flags[EXITONNOEVENTS]) {
		ret = errno = EINVAL;
		error("Conflicting options: \"--only-initialsync\" and \"--exit-on-no-events\" cannot be used together.");
	}

	if (ctx_p->flags[SKIPINITSYNC] && ctx_p->flags[ONLYINITSYNC]) {
		ret = errno = EINVAL;
		error("Conflicting options: \"--skip-initialsync\" and \"--only-initialsync\" cannot be used together.");
	}

	if (ctx_p->flags[INITFULL] && ctx_p->flags[SKIPINITSYNC]) {
		ret = errno = EINVAL;
		error("Conflicting options: \"--full-initialsync\" and \"--skip-initialsync\" cannot be used together.");
	}
	if (ctx_p->flags[MODSIGN] && (ctx_p->flags[CANCEL_SYSCALLS]&CSC_MON_STAT)) {
		ret = errno = EINVAL;
		error("Conflicting options: \"--modification-signature\" and \"--cancel-syscalls=mon_stat\" cannot be used together.");
	}

	if (ctx_p->flags[EXCLUDEMOUNTPOINTS])
		ctx_p->flags[ONEFILESYSTEM]=1;

	if (ctx_p->flags[MODE] == MODE_UNSET) {
		ret = errno = EINVAL;
		error("\"--mode\" is not set.");
	}

	if (ctx_p->watchdir == NULL) {
		ret = errno = EINVAL;
		error("\"--watch-dir\" is not set.");
	}

	if (ctx_p->handlerfpath == NULL) {
		switch (ctx_p->flags[MODE]) {
			case MODE_DIRECT:
				ctx_p->handlerfpath = DEFAULT_CP_PATH;
				break;
			case MODE_RSYNCDIRECT:
				ctx_p->handlerfpath = DEFAULT_RSYNC_PATH;
				break;
			default:
				ret = errno = EINVAL;
				error("\"--sync-handler\" path is not set.");
		}
	}
/*
	if (ctx_p->flags[SYNCHANDLERSO] && ctx_p->flags[RSYNC]) {
		ret = EINVAL;
		ret = errno = EINVAL;
		error("Option \"--rsync\" cannot be used in conjunction with \"--synchandler-so-module\".");
	}
*/
//	if (ctx_p->flags[SYNCHANDLERSO] && (ctx_p->listoutdir != NULL))
//		error("Warning: Option \"--dir-lists\" has no effect conjunction with \"--synchandler-so-module\".");

//	if (ctx_p->flags[SYNCHANDLERSO] && (ctx_p->destdir != NULL))
//		error("Warning: Destination directory argument has no effect conjunction with \"--synchandler-so-module\".");

	if ((ctx_p->flags[MODE] == MODE_RSYNCDIRECT) && (ctx_p->destdir == NULL)) {
		ret = errno = EINVAL;
		error("Mode \"rsyncdirect\" cannot be used without specifying \"--dest-dir\".");
	}

#ifdef CLUSTER_SUPPORT
	if ((ctx_p->flags[MODE] == MODE_RSYNCDIRECT ) && (ctx_p->cluster_iface != NULL)) {
		ret = errno = EINVAL;
		error("Mode \"rsyncdirect\" cannot be used in conjunction with \"--cluster-iface\".");
	}

	if ((ctx_p->cluster_iface == NULL) && ((ctx_p->cluster_mcastipaddr != NULL) || (ctx_p->cluster_nodename != NULL) || (ctx_p->cluster_timeout) || (ctx_p->cluster_mcastipport))) {
		ret = errno = EINVAL;
		error("ctx \"--cluster-ip\", \"--cluster-node-name\", \"--cluster_timeout\" and/or \"cluster_ipport\" cannot be used without \"--cluster-iface\".");
	}

	if (ctx_p->cluster_hash_dl_min > ctx_p->cluster_hash_dl_max) {
		ret = errno = EINVAL;
		error("\"--cluster-hash-dl-min\" cannot be greater than \"--cluster-hash-dl-max\".");
	}

	if (ctx_p->cluster_hash_dl_max > ctx_p->cluster_scan_dl_max) {
		ret = errno = EINVAL;
		error("\"--cluster-hash-dl-max\" cannot be greater than \"--cluster-scan-dl-max\".");
	}

	if (!ctx_p->cluster_timeout)
		ctx_p->cluster_timeout	    = DEFAULT_CLUSTERTIMEOUT;
	if (!ctx_p->cluster_mcastipport)
		ctx_p->cluster_mcastipport = DEFAULT_CLUSTERIPPORT;
	if (!ctx_p->cluster_mcastipaddr)
		ctx_p->cluster_mcastipaddr = DEFAULT_CLUSTERIPADDR;

	if (ctx_p->cluster_iface != NULL) {
#ifndef _DEBUG_FORCE
		ret = errno = EINVAL;
		error("Cluster subsystem is not implemented, yet. Sorry.");
#endif
		if (ctx_p->cluster_nodename == NULL) {

			if(!uname(&utsname))
				ctx_p->cluster_nodename = strdup(utsname.nodename);

			debug(1, "cluster node name is: %s", ctx_p->cluster_nodename);
		}
		if (ctx_p->cluster_nodename == NULL) {
			ret = errno = EINVAL;
			error("Option \"--cluster-iface\" is set, but \"--cluster-node-name\" is not set and cannot get the nodename with uname().");
		} else {
			ctx_p->cluster_nodename_len = strlen(ctx_p->cluster_nodename);
		}
	}
#endif // CLUSTER_SUPPORT

	switch (ctx_p->flags[MODE]) {
		case MODE_RSYNCSO:
			ctx_p->synchandler_argf |= SHFL_EXCLUDE_LIST_PATH;
			ctx_p->synchandler_argf |= SHFL_INCLUDE_LIST_PATH;
			break;
	}

	if (
		ctx_p->flags[RSYNCPREFERINCLUDE] && 
		!(
			ctx_p->flags[MODE] == MODE_RSYNCDIRECT ||
			ctx_p->flags[MODE] == MODE_RSYNCSHELL  ||
			ctx_p->flags[MODE] == MODE_RSYNCSO
		)
	)
		warning("Option \"--rsyncpreferinclude\" is useless if mode is not \"rsyncdirect\", \"rsyncshell\" or \"rsyncso\".");

#ifdef AUTORULESW
	if (
		(
			ctx_p->flags[MODE] == MODE_RSYNCDIRECT ||
			ctx_p->flags[MODE] == MODE_RSYNCSHELL  ||
			ctx_p->flags[MODE] == MODE_RSYNCSO
		)
		&& ctx_p->flags[AUTORULESW]
	)
		warning("Option \"--auto-add-rules-w\" in modes \"rsyncdirect\", \"rsyncshell\" and \"rsyncso\" may cause unexpected problems.");
#endif

/*
	if(ctx_p->flags[HAVERECURSIVESYNC] && (ctx_p->listoutdir == NULL)) {
		error("Option \"--dir-lists\" should be set to use option \"--have-recursive-sync\".");
		ret = EINVAL;
	}
*/

	if (
		ctx_p->flags[HAVERECURSIVESYNC] &&
		(
			ctx_p->flags[MODE] == MODE_RSYNCDIRECT ||
			ctx_p->flags[MODE] == MODE_RSYNCSHELL  ||
			ctx_p->flags[MODE] == MODE_RSYNCSO
		)
	) {
		ret = errno = EINVAL;
		error("Option \"--have-recursive-sync\" with nodes \"rsyncdirect\", \"rsyncshell\" and \"rsyncso\" are incompatible.");
	}

	if (ctx_p->flags[SYNCLISTSIMPLIFY] && (ctx_p->listoutdir == NULL)) {
		ret = errno = EINVAL;
		error("Option \"--dir-lists\" should be set to use option \"--synclist-simplify\".");
	}

	if (
		ctx_p->flags[SYNCLISTSIMPLIFY] && 
		(
			ctx_p->flags[MODE] == MODE_RSYNCDIRECT ||
			ctx_p->flags[MODE] == MODE_RSYNCSHELL  ||
			ctx_p->flags[MODE] == MODE_RSYNCSO
		)
	) {
		ret = errno = EINVAL;
		error("Option \"--synclist-simplify\" with nodes \"rsyncdirect\" and \"rsyncshell\" are incompatible.");
	}
#ifdef GIO_SUPPORT
# ifdef SECCOMP_SUPPORT
	if ((ctx_p->flags[MONITOR] == NE_GIO) && (ctx_p->flags[SECCOMP_FILTER])) {
		ret = errno = EINVAL;
		error("GIO is not compatible with seccomp filter (\"--monitor=gio\" and \"--seccomp-filter\" are incompatible)");
	}
# endif
#endif

#ifdef FANOTIFY_SUPPORT
	if (ctx_p->flags[MONITOR] == NE_FANOTIFY)
		critical("fanotify is not supported, now!");
	else
#endif
	switch (ctx_p->flags[MONITOR]) {
#ifdef INOTIFY_SUPPORT
		case NE_INOTIFY:
#endif
#ifdef FANOTIFY_SUPPORT
		case NE_FANOTIFY:
#endif
#ifdef KQUEUE_SUPPORT
		case NE_KQUEUE:
#endif
#ifdef BSM_SUPPORT
		case NE_BSM:
		case NE_BSM_PREFETCH:
#endif
#ifdef GIO_SUPPORT
		case NE_GIO:
#endif
#ifdef DTRACEPIPE_SUPPORT
		case NE_DTRACEPIPE:
#endif
			break;
		default:
			ret = errno = EINVAL;
			error("Required one of the next options:"
#ifdef INOTIFY_SUPPORT
				" \"--monitor=inotify\""
#endif
#ifdef FANOTIFY_SUPPORT
				" \"--monitor=fanotify\""
#endif
#ifdef KQUEUE_SUPPORT
				" \"--monitor=kqueue\""
#endif
#ifdef BSM_SUPPORT
				" \"--monitor=bsm\""
#endif
#ifdef GIO_SUPPORT
				" \"--monitor=gio\""
#endif
#ifdef DTRACEPIPE_SUPPORT
				" \"--monitor=dtracepipe\""
#endif
			);
	}

	if (ctx_p->flags[EXITHOOK]) {
#ifdef VERYPARANOID
		if (ctx_p->exithookfile == NULL) {
			ret = errno = EINVAL;
			error("ctx_p->exithookfile == NULL");
		} else 
#endif
		{
			if (access(ctx_p->exithookfile, X_OK) == -1) {
				error("\"%s\" is not executable.", ctx_p->exithookfile);
				if (!ret)
					ret = errno;
			}
		}
	}

	if (ctx_p->flags[CHECK_EXECVP_ARGS] && (ctx_p->flags[MODE] == MODE_DIRECT)) {
		ret = errno = EINVAL;
		error("Options --check-execvp-arguments/--secure-splitting cannot be used in conjuction with --mode=direct (see \"man 1 clsync\": --check-execvp-arguments).");
	}

#if 0
	if (ctx_p->handlerfpath != NULL)
		if (access(ctx_p->handlerfpath, X_OK) == -1) {
			error("\"%s\" is not executable.", ctx_p->handlerfpath);
			if (!ret)
				ret = errno;
		}
#endif

	return ret;
}

int config_block_parse(ctx_t *ctx_p, const char *const config_block_name)
{
	int rc;
	debug(1, "(ctx_p, \"%s\")", config_block_name);

	ctx_p->config_block = config_block_name;
	rc = configs_parse(ctx_p, PS_CONTROL);

	if (!rc)
		rc = ctx_check(ctx_p);

	return errno = rc;
}

int ctx_set(ctx_t *ctx_p, const char *const parameter_name, const char *const parameter_value)
{
	int ret = ENOENT;
	const struct option *lo_ptr = long_options;

	while (lo_ptr->name != NULL) {
		if (!strcmp(lo_ptr->name, parameter_name)) {
			ret = parse_parameter(ctx_p, lo_ptr->val, strdup(parameter_value), PS_CONTROL);
			break;
		}
		lo_ptr++;
	}

	ret = ctx_check(ctx_p);
	if (ret)
		critical("Cannot continue with this setup");

	return ret;
}

void ctx_cleanup(ctx_t *ctx_p) {
	int i=0;
	debug(9, "");

	while (i < OPTION_FLAGS) {
		if (ctx_p->flags_values_raw[i] != NULL) {
			free(ctx_p->flags_values_raw[i]);
			ctx_p->flags_values_raw[i] = NULL;
		}
		i++;
	}

	{
		int n = 0;
		while (n < SHARGS_MAX) {
			int i = 0,  e = ctx_p->synchandler_args[n].c;
			while (i < e) {
#ifdef _DEBUG_FORCE
				debug(14, "synchandler args: %u, %u: free(%p)", n, i, ctx_p->synchandler_args[n].v[i]);
#endif
				free(ctx_p->synchandler_args[n].v[i]);
				ctx_p->synchandler_args[n].v[i] = NULL;
				i++;
			}
			ctx_p->synchandler_args[n].c = 0;
			n++;
		}
	}

	return;
}

int becomedaemon() {
	int pid;
	signal(SIGPIPE, SIG_IGN);
	switch((pid = fork())) {
		case -1:
			error("Cannot fork().");
			return(errno);
		case 0:
			setsid();
			break;
		default:
			debug(1, "fork()-ed, pid is %i.", pid);
			errno=0;
			exit(0);
	}
	return 0;
}

int main_cleanup(ctx_t *ctx_p) {
	int i=0;
	while((i < MAXRULES) && (ctx_p->rules[i].mask != RA_NONE))
		regfree(&ctx_p->rules[i++].expr);

	debug(3, "%i %i %i %i", ctx_p->watchdirsize, ctx_p->watchdirwslashsize, ctx_p->destdirsize, ctx_p->destdirwslashsize);

	return 0;
}

int main_rehash(ctx_t *ctx_p) {
	debug(3, "");
	int ret=0;

	main_cleanup(ctx_p);

	if(ctx_p->rulfpath != NULL) {
		ret = parse_rules_fromfile(ctx_p);
		if(ret)
			error("Got error from parse_rules_fromfile().");
	} else {
		ctx_p->rules[0].perm = DEFAULT_RULES_PERM;
		ctx_p->rules[0].mask = RA_NONE;		// Terminator. End of rules.
	}

	return ret;
}

FILE *main_statusfile_f;
int main_status_update(ctx_t *ctx_p) {
	static state_t state_old = STATE_UNKNOWN;
	state_t        state     = ctx_p->state;

	debug(4, "%u", state);

	if (state == state_old) {
		debug(3, "State unchanged: %u == %u", state, state_old);
		return 0;
	}

#ifdef VERYPARANOID
	if (status_descr[state] == NULL) {
		error("status_descr[%u] == NULL.", state);
		return EINVAL;
	}
#endif

	setenv("CLSYNC_STATUS", status_descr[state], 1);

	if (ctx_p->statusfile == NULL)
		return 0;

	debug(3, "Setting status to %i: %s.", state, status_descr[state]);
	state_old=state;

	int ret = 0;

	if (ftruncate(fileno(main_statusfile_f), 0)) {
		error("Cannot ftruncate() the file \"%s\".",
			ctx_p->statusfile);
		return errno;
	}
	rewind(main_statusfile_f);
	if (fprintf(main_statusfile_f, "%s", status_descr[state]) <= 0) {	// TODO: check output length
		error("Cannot write to file \"%s\".",
			ctx_p->statusfile);
		return errno;
	}
	if (fflush(main_statusfile_f)) {
		error("Cannot fflush() on file \"%s\".",
			ctx_p->statusfile);
		return errno;
	}

	return ret;
}

int argc;
char **argv;
#define UGID_PRESERVE (1<<16)
int main(int _argc, char *_argv[]) {
	struct ctx *ctx_p = xcalloc(1, sizeof(*ctx_p));

	argv = _argv;
	argc = _argc;

	int ret = 0, nret, rm_listoutdir = 0;

	SAFE (posixhacks_init(), errno = ret = _SAFE_rc);

	ctx_p->flags[MONITOR]			 = DEFAULT_NOTIFYENGINE;
	ctx_p->syncdelay 			 = DEFAULT_SYNCDELAY;
	ctx_p->_queues[QUEUE_NORMAL].collectdelay   = DEFAULT_COLLECTDELAY;
	ctx_p->_queues[QUEUE_BIGFILE].collectdelay  = DEFAULT_BFILECOLLECTDELAY;
	ctx_p->_queues[QUEUE_INSTANT].collectdelay  = COLLECTDELAY_INSTANT;
	ctx_p->_queues[QUEUE_LOCKWAIT].collectdelay = COLLECTDELAY_INSTANT;
	ctx_p->bfilethreshold			 = DEFAULT_BFILETHRESHOLD;
	ctx_p->rsyncinclimit			 = DEFAULT_RSYNCINCLUDELINESLIMIT;
	ctx_p->synctimeout			 = DEFAULT_SYNCTIMEOUT;
#ifdef CLUSTER_SUPPORT
	ctx_p->cluster_hash_dl_min		 = DEFAULT_CLUSTERHDLMIN;
	ctx_p->cluster_hash_dl_max		 = DEFAULT_CLUSTERHDLMAX;
	ctx_p->cluster_scan_dl_max		 = DEFAULT_CLUSTERSDLMAX;
#endif
	ctx_p->config_block			 = DEFAULT_CONFIG_BLOCK;
	ctx_p->retries				 = DEFAULT_RETRIES;
	ctx_p->flags[VERBOSE]			 = DEFAULT_VERBOSE;
#ifdef PIVOTROOT_OPT_SUPPORT
	ctx_p->flags[PIVOT_ROOT]		 = DEFAULT_PIVOT_MODE;
#endif
#ifdef CAPABILITIES_SUPPORT
	ctx_p->flags[CAP_PRESERVE]		 = CAP_PRESERVE_TRY;
	ctx_p->caps				 = DEFAULT_PRESERVE_CAPABILITIES;
	ctx_p->flags[CAPS_INHERIT]		 = DEFAULT_CAPS_INHERIT;
	ctx_p->flags[DETACH_IPC]		 = DEFAULT_DETACH_IPC;
	parse_parameter(ctx_p, LABEL, strdup(DEFAULT_LABEL), PS_DEFAULTS);

	ncpus					 = sysconf(_SC_NPROCESSORS_ONLN); // Get number of available logical CPUs

	memory_init();

	{
		struct passwd *pwd = getpwnam(DEFAULT_USER);
		ctx_p->uid = (pwd != NULL) ? pwd->pw_uid : DEFAULT_UID;
		ctx_p->flags[UID]		 = UGID_PRESERVE;
	}
	{
		struct group  *grp = getgrnam(DEFAULT_GROUP);
		ctx_p->gid = (grp != NULL) ? grp->gr_gid : DEFAULT_GID;
		ctx_p->flags[GID]		 = UGID_PRESERVE;
	}
#endif

	ctx_p->pid				 = getpid();

	error_init(&ctx_p->flags[OUTPUT_METHOD], &ctx_p->flags[QUIET], &ctx_p->flags[VERBOSE], &ctx_p->flags[DEBUG]);

	nret = arguments_parse(argc, argv, ctx_p);
	if (nret) ret = nret;

	if (!ret) {
		nret = configs_parse(ctx_p, PS_CONFIG);
		if(nret) ret = nret;
	}

	if (!ctx_p->flags[PRIVILEGEDUID])
		ctx_p->privileged_uid = getuid();
	if (!ctx_p->flags[PRIVILEGEDGID])
		ctx_p->privileged_gid = getgid();

	if (!ctx_p->flags[SYNCHANDLERUID])
		ctx_p->synchandler_uid = ctx_p->privileged_uid;
	if (!ctx_p->flags[SYNCHANDLERGID])
		ctx_p->synchandler_gid = ctx_p->privileged_gid;

#ifdef CGROUP_SUPPORT
	if (ctx_p->cg_groupname == NULL) {
		ctx_p->cg_groupname = parameter_expand(ctx_p, strdup(DEFAULT_CG_GROUPNAME), 2, NULL, NULL, parameter_get, ctx_p);
		ctx_p->flags_values_raw[CG_GROUPNAME] = ctx_p->cg_groupname;
	}
#endif

	if (ctx_p->dump_path == NULL) {
		ctx_p->dump_path = parameter_expand(ctx_p, strdup(DEFAULT_DUMPDIR), 2, NULL, NULL, parameter_get, ctx_p);
		ctx_p->flags_values_raw[DUMPDIR] = ctx_p->dump_path;
	}

	if (!ctx_p->synchandler_args[SHARGS_PRIMARY].c) {
		char *args_line0 = NULL, *args_line1 = NULL;
		switch (ctx_p->flags[MODE]) {
			case MODE_SIMPLE:
				args_line0 = DEFAULT_SYNCHANDLER_ARGS_SIMPLE;
				break;
			case MODE_DIRECT:
				args_line0 = DEFAULT_SYNCHANDLER_ARGS_DIRECT;
				break;
			case MODE_SHELL:
				args_line0 = DEFAULT_SYNCHANDLER_ARGS_SHELL_NR;
				args_line1 = DEFAULT_SYNCHANDLER_ARGS_SHELL_R;
				break;
			case MODE_RSYNCDIRECT:
				args_line0 = (ctx_p->flags[RSYNCPREFERINCLUDE]) ? DEFAULT_SYNCHANDLER_ARGS_RDIRECT_I : DEFAULT_SYNCHANDLER_ARGS_RDIRECT_E;
				break;
			case MODE_RSYNCSHELL:
				args_line0 = (ctx_p->flags[RSYNCPREFERINCLUDE]) ? DEFAULT_SYNCHANDLER_ARGS_RSHELL_I  : DEFAULT_SYNCHANDLER_ARGS_RSHELL_E;
				break;
			default:
				break;
		}

		if (args_line0 != NULL) {
			char *args_line = strdup(args_line0);
			parse_parameter(ctx_p, SYNCHANDLERARGS0, args_line, PS_DEFAULTS);
		}

		if (args_line1 != NULL) {
			char *args_line = strdup(args_line1);
			parse_parameter(ctx_p, SYNCHANDLERARGS1, args_line, PS_DEFAULTS);
		}
	}

	debug(4, "ncpus == %u", ncpus);
	debug(4, "debugging flags: %u %u %u %u", ctx_p->flags[OUTPUT_METHOD], ctx_p->flags[QUIET], ctx_p->flags[VERBOSE], ctx_p->flags[DEBUG]);

	if (ctx_p->watchdir != NULL) {
		char *rwatchdir = realpath(ctx_p->watchdir, NULL);
		if (rwatchdir == NULL) {
			error("Got error while realpath() on \"%s\" [#0].", ctx_p->watchdir);
			ret = errno;
		}
		debug(5, "rwatchdir == \"%s\"", rwatchdir);

		stat64_t stat64={0};
		if (lstat64(ctx_p->watchdir, &stat64)) {
			error("Cannot lstat64() on \"%s\"", ctx_p->watchdir);
			if (!ret)
				ret = errno;
		} else {
			ctx_p->st_dev = stat64.st_dev;
/*
			if ((stat64.st_mode & S_IFMT) == S_IFLNK) {
				// The proplems may be due to FTS_PHYSICAL option of fts_open() in sync_initialsync_rsync_walk(),
				// so if the "watch dir" is just a symlink it doesn't walk recursivly. For example, in "-R" case
				// it disables filters, because exclude-list will be empty.
#ifdef VERYPARANOID
				error("Watch dir cannot be symlink, but \"%s\" is a symlink.", ctx_p->watchdir);
				ret = EINVAL;
#else
				char *watchdir_resolved_part = xcalloc(1, PATH_MAX+2);
				ssize_t r = readlink(ctx_p->watchdir, watchdir_resolved_part, PATH_MAX+1);
	
				if (r>=PATH_MAX) {	// TODO: check if it's possible
					ret = errno = EINVAL;
					error("Too long file path resolved from symbolic link \"%s\"", ctx_p->watchdir);
				} else
				if (r<0) {
					error("Cannot resolve symbolic link \"%s\": readlink() error", ctx_p->watchdir);
					ret = EINVAL;
				} else {
					char *watchdir_resolved;
# ifdef PARANOID
					if (ctx_p->watchdirsize)
						if (ctx_p->watchdir != NULL)
							free(ctx_p->watchdir);
# endif

					size_t watchdir_resolved_part_len = strlen(watchdir_resolved_part);
					ctx_p->watchdirsize = watchdir_resolved_part_len+1;	// Not true for case of relative symlink
					if (*watchdir_resolved_part == '/') {
						// Absolute symlink
						watchdir_resolved = malloc(ctx_p->watchdirsize);
						memcpy(watchdir_resolved, watchdir_resolved_part, ctx_p->watchdirsize);
					} else {
						// Relative symlink :(
						char *rslash = strrchr(ctx_p->watchdir, '/');

						char *watchdir_resolved_rel  = xmalloc(PATH_MAX+2);
						size_t watchdir_resolved_rel_len = rslash-ctx_p->watchdir + 1;
						memcpy(watchdir_resolved_rel, ctx_p->watchdir, watchdir_resolved_rel_len);
						memcpy(&watchdir_resolved_rel[watchdir_resolved_rel_len], watchdir_resolved_part, watchdir_resolved_part_len+1);

						watchdir_resolved = realpath(watchdir_resolved_rel, NULL);

						free(watchdir_resolved_rel);
					}

					
					debug(1, "Symlink resolved: watchdir \"%s\" -> \"%s\"", ctx_p->watchdir, watchdir_resolved);
					ctx_p->watchdir = watchdir_resolved;
				}
				free(watchdir_resolved_part);
#endif // VERYPARANOID else
			}
*/
		}

		if (!ret) {
			parse_parameter(ctx_p, WATCHDIR, rwatchdir, PS_CORRECTION);
			ctx_p->watchdirlen  = strlen(ctx_p->watchdir);
			ctx_p->watchdirsize = ctx_p->watchdirlen;

#ifdef VERYPARANOID
			if (ctx_p->watchdirlen == 1) {
				ret = errno = EINVAL;
				error("Very-Paranoid: --watch-dir is supposed to be not \"/\".");
			}
#endif
		}

		if (!ret) {
			if (ctx_p->watchdirlen == 1) {
				ctx_p->watchdirwslash     = ctx_p->watchdir;
				ctx_p->watchdirwslashsize = 0;
				ctx_p->watchdir_dirlevel  = 0;
			} else {
				size_t size = ctx_p->watchdirlen + 2;
				char *newwatchdir = xmalloc(size);
				memcpy( newwatchdir, ctx_p->watchdir, ctx_p->watchdirlen);
				ctx_p->watchdirwslash     = newwatchdir;
				ctx_p->watchdirwslashsize = size;
				memcpy(&ctx_p->watchdirwslash[ctx_p->watchdirlen], "/", 2);

				ctx_p->watchdir_dirlevel  = fileutils_calcdirlevel(ctx_p->watchdirwslash);
			}
		}
	}

	if ((ctx_p->destdir != NULL) && (ctx_p->destproto == NULL)) {	// "ctx_p->destproto == NULL" means "no protocol"/"local directory"
		char *rdestdir = realpath(ctx_p->destdir, NULL);
		if (rdestdir == NULL) {
			error("Got error while realpath() on \"%s\" [#1].", ctx_p->destdir);
			ret = errno;
		}
		debug(5, "rdestdir == \"%s\"", rdestdir);

		if (!ret) {
			parse_parameter(ctx_p, DESTDIR, rdestdir, PS_CORRECTION);
			ctx_p->destdirlen  = strlen(ctx_p->destdir);
			ctx_p->destdirsize = ctx_p->destdirlen;

			if (ctx_p->destdirlen == 1) {
				ret = errno = EINVAL;
				error("destdir is supposed to be not \"/\".");
			}
		}

		if (!ret) {
			size_t size = ctx_p->destdirlen  + 2;
			char *newdestdir  = xmalloc(size);
			memcpy( newdestdir,  ctx_p->destdir,  ctx_p->destdirlen);
			ctx_p->destdirwslash     = newdestdir;
			ctx_p->destdirwslashsize = size;
			memcpy(&ctx_p->destdirwslash[ctx_p->destdirlen], "/", 2);
		}
	} else
	if (ctx_p->destproto != NULL)
		ctx_p->destdirwslash = ctx_p->destdir;

	if (ctx_p->rulfpath) {
		if (*ctx_p->rulfpath != '/') {
			ctx_p->rulfpath     = realpath(ctx_p->rulfpath, NULL);

			if (ctx_p->rulfpath == NULL)
				error("Cannot find rules-file. Got error while realpath(\"%s\")", ctx_p->rulfpath);
			else
				ctx_p->rulfpathsize = 1;
		}
	}

	if (ctx_p->handlerfpath != NULL) {
		char *rhandlerfpath = realpath(ctx_p->handlerfpath, NULL);
/*
		if (rhandlerfpath == NULL) {
			error("Got error while realpath() on \"%s\" [#0].", ctx_p->handlerfpath);
			ret = errno;
		}
		debug(5, "rhandlerfpath == \"%s\"", rhandlerfpath);
		ctx_p->handlerfpath = rhandlerfpath;*/

		if (rhandlerfpath != NULL) 
			ctx_p->handlerfpath = rhandlerfpath;

	}

	debug(9, "chdir(\"%s\");", ctx_p->watchdir);
	if (chdir(ctx_p->watchdir)) {
		error("Got error while chdir(\"%s\")", ctx_p->watchdir);
		ret = errno;
	}
/*
	if (ctx_p->flags_values_raw[SYNCHANDLERARGS0] != NULL)
		parse_parameter(ctx_p, SYNCHANDLERARGS0, NULL, PS_REHASH);

	if (ctx_p->flags_values_raw[SYNCHANDLERARGS1] != NULL)
		parse_parameter(ctx_p, SYNCHANDLERARGS1, NULL, PS_REHASH);
*/
	{
		int n = 0;
		while (n < SHARGS_MAX) {
			synchandler_args_t *args_p = &ctx_p->synchandler_args[n++];
			debug(9, "Custom arguments %u count: %u", n-1, args_p->c);
			int i = 0;
			while (i < args_p->c) {
				int macros_count = -1, expanded = -1;

				args_p->v[i] = parameter_expand(ctx_p, args_p->v[i], 4, &macros_count, &expanded, parameter_get_wmacro, ctx_p);

				debug(12, "args_p->v[%u] == \"%s\" (t: %u; e: %u)", i, args_p->v[i], macros_count, expanded);
				if (macros_count == expanded)
					args_p->isexpanded[i]++;
				i++;
			}
		}
	}

	ctx_p->state = STATE_STARTING;

	{
#ifdef GETMNTENT_SUPPORT
		struct mntent *ent;
		FILE *ent_f;

		ent_f = NULL;
		if (ctx_p->mountpoints) {
			// Openning the file with mount list
			ent_f = setmntent("/proc/mounts", "r");
			if (ent_f == NULL) {
				error("Got error while setmntent(\"/proc/mounts\", \"r\")");
				ret = errno;
			}
		}
#endif

#ifdef UNSHARE_SUPPORT
#define unshare_wrapper(a) \
		if (unshare(a)) {\
			error("Got error from unshare("TOSTR(a)")");\
			ret = errno;\
		}
		if (ctx_p->flags[DETACH_IPC]) {
			unshare(CLONE_NEWUTS);
			error_init_ipc(ctx_p->flags[SPLITTING] == SM_PROCESS ? IPCT_SHARED : IPCT_PRIVATE);
		}
		if (ctx_p->flags[DETACH_MISCELLANEA]) {
			unshare(CLONE_NEWIPC);
			unshare(CLONE_NEWUTS);
			unshare(CLONE_SYSVSEM);
		}
		if ((ctx_p->flags[PIVOT_ROOT] != PW_OFF) || ctx_p->mountpoints) {
			unshare_wrapper(CLONE_FILES);
			unshare_wrapper(CLONE_FS);
			unshare_wrapper(CLONE_NEWNS);
		}
		if (ctx_p->flags[DETACH_NETWORK] == DN_EVERYWHERE)
			unshare_wrapper(CLONE_NEWNET);
#undef unshare_wrapper
#endif

		if (ctx_p->chroot_dir != NULL) {
#ifdef PIVOTROOT_OPT_SUPPORT
			switch (ctx_p->flags[PIVOT_ROOT]) {
				case PW_OFF:
				case PW_DIRECT:
					break;
				case PW_AUTO:
				case PW_AUTORO: {
					if (chdir(ctx_p->chroot_dir)) {
						error("Got error while chdir(\"%s\")", ctx_p->chroot_dir);
						ret = errno;
					}

					if (mkdir("old_root", 0700)) {
						if (errno != EEXIST) {
							error("Got error from mkdir(\"old_root\", 0700)");
							ret = errno;
							break;
						}
					}

					if (mkdir(PIVOT_AUTO_DIR, 0700)) {
						if (errno != EEXIST) {
							error("Got error from mkdir(\""PIVOT_AUTO_DIR"\", 0700)");
							ret = errno;
							break;
						}
					}

					unsigned long mount_flags  =  MS_BIND | MS_REC |
						((ctx_p->flags[PIVOT_ROOT] == PW_AUTORO) ? MS_RDONLY : 0);

					if (mount(ctx_p->chroot_dir, PIVOT_AUTO_DIR, NULL, mount_flags, NULL)) {
						error("Got error while mount(\"%s\", \"%s\", NULL, %o, NULL)",
							ctx_p->chroot_dir, PIVOT_AUTO_DIR, mount_flags);
						ret = errno;
						break;
					}

					ctx_p->chroot_dir = PIVOT_AUTO_DIR;
					break;
				}
			}
#endif

			debug(7, "chdir(\"%s\")", ctx_p->chroot_dir);
			if (chdir(ctx_p->chroot_dir)) {
				error("Got error while chdir(\"%s\")", ctx_p->chroot_dir);
				ret = errno;
			}

		}

#ifdef GETMNTENT_SUPPORT
		if (ctx_p->mountpoints && (ent_f != NULL)) {
			// Getting mount-points to be umounted
			while (NULL != (ent = getmntent(ent_f))) {
				int i;
				debug(8, "Checking should \"%s\" be umount or not", ent->mnt_dir);

				i=0;
				while (i < ctx_p->mountpoints) {
					debug(9, "\"%s\" <?> \"%s\"", ent->mnt_dir, ctx_p->mountpoint[i]);
					if (!strcmp(ent->mnt_dir, ctx_p->mountpoint[i])) {
						debug(9, "found");
						break;
					}

					i++;
				}

				if (i >= ctx_p->mountpoints) {
					debug(1, "umount2(\"%s\", MNT_DETACH)", ent->mnt_dir);
					if (umount2(ent->mnt_dir, MNT_DETACH) && errno != ENOENT && errno != EINVAL) {
						error("Got error while umount2(\"%s\", MNT_DETACH)", ent->mnt_dir);
						ret = errno;
					}
				}
			}

			endmntent(ent_f);
		}
#endif
		if (ctx_p->chroot_dir != NULL) {
#ifdef PIVOTROOT_OPT_SUPPORT
			if (!ret) {
				switch (ctx_p->flags[PIVOT_ROOT]) {
					case PW_OFF:
						break;
					case PW_DIRECT:
					case PW_AUTO:
					case PW_AUTORO:
						if (pivot_root(".", "old_root")) {
							error("Got error while pivot_root(\".\", \"old_root\")");
							ret = errno;
						}
						break;
				}
			}
#endif
			debug(7, "chroot(\".\")");
			if (chroot(".")) {
				error("Got error while chroot(\".\")");
				ret = errno;
			}
#ifdef PIVOTROOT_OPT_SUPPORT
			if (!ret) {
				switch (ctx_p->flags[PIVOT_ROOT]) {
					case PW_OFF:
						break;
					case PW_DIRECT:
					case PW_AUTO:
					case PW_AUTORO:
						if (umount2("old_root", MNT_DETACH)) {
							error("Got error while umount2(\"old_root\", MNT_DETACH)");
							ret = errno;
						}
						break;
				}
			}
#endif
		}
	}

	if (ctx_p->statusfile != NULL) {
		debug(1, "Trying to open the status file for writing.");
		main_statusfile_f = fopen(ctx_p->statusfile, "w");
		if (main_statusfile_f != NULL) {
			uid_t uid = ctx_p->flags[UID] ? ctx_p->uid : getuid();
			gid_t gid = ctx_p->flags[GID] ? ctx_p->gid : getgid();
			debug(1, "Changing owner of the status file to %u:%u", uid, gid);
			if (fchown(fileno(main_statusfile_f), uid, gid))
				warning("Cannot fchown(%u -> \"%s\", %u, %u)",
					fileno(main_statusfile_f), ctx_p->statusfile, uid, gid);
			main_status_update(ctx_p);
		}
	}


#ifdef CAPABILITIES_SUPPORT
	debug(1, "Preserving Linux capabilities");

	// Tell kernel not clear capabilities when dropping root 
	if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
		error("Cannot prctl(PR_SET_KEEPCAPS, 1) to preserve capabilities");
		ret = errno;
	}
#endif

#ifdef CGROUP_SUPPORT
	if (ctx_p->flags[FORBIDDEVICES]) {
		error_on(clsync_cgroup_init(ctx_p));
		error_on(clsync_cgroup_forbid_extra_devices());
		error_on(clsync_cgroup_attach(ctx_p));
	}
#endif

	nret=main_rehash(ctx_p);
	if (nret)
		ret = nret;

	if (ctx_p->flags[GID]) {
		int rc;
		debug(3, "Trying to drop effective gid to %i", ctx_p->gid);
		rc = setegid(ctx_p->gid);

		if (rc && (ctx_p->flags[GID] != UGID_PRESERVE)) {
			error("Cannot setegid(%u)", ctx_p->gid);
			ret = errno;
		}
	}

	if (ctx_p->flags[UID]) {
		int rc;
		debug(3, "Trying to drop effective uid to %i", ctx_p->uid);
		rc = seteuid(ctx_p->uid);

		if (rc && (ctx_p->flags[UID] != UGID_PRESERVE)) {
			error("Cannot seteuid(%u)", ctx_p->uid);
			ret = errno;
		}
	}

	if (main_statusfile_f == NULL && ctx_p->statusfile != NULL) {
		debug(1, "Trying to open the status file for writing (after setuid()/setgid()).");
		main_statusfile_f = fopen(ctx_p->statusfile, "w");
		if (main_statusfile_f == NULL) {
			error("Cannot open file \"%s\" for writing.", 
				ctx_p->statusfile);
			ret = errno;
		} 
	}

	debug(1, "%s [%s] (%p) -> %s [%s] (%p)", ctx_p->watchdir, ctx_p->watchdirwslash, ctx_p->watchdirwslash, ctx_p->destdir?ctx_p->destdir:"", ctx_p->destdirwslash?ctx_p->destdirwslash:"", ctx_p->destdirwslash);

	{
		int rc = ctx_check(ctx_p);
		if (!ret) ret = rc;
	}

	if (
		(ctx_p->listoutdir == NULL) && 
		(
			ctx_p->synchandler_argf & 
			(
				SHFL_INCLUDE_LIST_PATH |
				SHFL_EXCLUDE_LIST_PATH
			)
		)
	) {
		char *template = strdup(TMPDIR_TEMPLATE);

		ctx_p->listoutdir = mkdtemp(template);

		if (ctx_p->listoutdir == NULL) {
			ret = errno;
			error("Cannot create temporary dir for list files");
		} else
			rm_listoutdir = 2;
	}

	if (ctx_p->listoutdir != NULL) {
		struct stat st={0};
		errno = 0;
		if (stat(ctx_p->listoutdir, &st)) {
			if (errno == ENOENT) {
				warning("Directory \"%s\" doesn't exist. Creating it.", ctx_p->listoutdir);
				errno = 0;
				if (mkdir(ctx_p->listoutdir, S_IRWXU)) {
					error("Cannot create directory \"%s\".", ctx_p->listoutdir);
					ret = errno;
				} else
					rm_listoutdir = 1;
			} else {
				error("Got error while stat() on \"%s\".", ctx_p->listoutdir);
				ret = errno;
			}
		}
		if (!errno)
			if (st.st_mode & (S_IRWXG|S_IRWXO)) {
#ifdef PARANOID
				ret = errno = EACCES;
				error("Insecure: Others have access to directory \"%s\". Exit.", ctx_p->listoutdir);
#else
				warning("Insecure: Others have access to directory \"%s\".", ctx_p->listoutdir);
#endif
			}
	}

	if (ctx_p->flags[BACKGROUND]) {
		nret = becomedaemon();
		if (nret)
			ret = nret;
	}

	if (ctx_p->pidfile != NULL) {
		debug(2, "Trying to open the pidfile \"%s\"", ctx_p->pidfile);
		pid_t pid = getpid();

		FILE *pidfile = fopen(ctx_p->pidfile, "w");
		if (pidfile == NULL) {
			// If error
			if (errno == EACCES) {
				int fd;
				uid_t euid = geteuid();
				gid_t egid = getegid();

				debug(1, "Don't have permissions to open file \"%s\". Trying seteuid(0)+open()+fchown()+close()+seteuid(%i)", ctx_p->pidfile, euid);

				errno  = 0;
				if (!errno) SAFE ( seteuid(0),							ret = errno );
				if (!errno) SAFE ( (fd = open(ctx_p->pidfile, O_CREAT|O_WRONLY, 0644)) == -1,	ret = errno );
				if (!errno) SAFE ( fchown(fd, euid, egid),					ret = errno );
				if (!errno) SAFE ( close(fd),							ret = errno );
				if (!errno) SAFE ( seteuid(euid),						ret = errno );

				pidfile = fopen(ctx_p->pidfile, "w");
			}

			if (pidfile == NULL) {
				error("Cannot open file \"%s\" to write a pid there",
					ctx_p->pidfile);
				ret = errno;
			}
		}

		if (pidfile != NULL) {
			if (fprintf(pidfile, "%u", pid) < 0) {
				error("Cannot write pid into file \"%s\"",
					ctx_p->pidfile);
				ret = errno;
			}
			fclose(pidfile);
		}
	}

	debug(3, "Current errno is %i.", ret);

	// == RUNNING ==
	if (ret == 0)
	         ret = sync_run(ctx_p);
	// == /RUNNING ==

	if (ctx_p->pidfile != NULL) {
		if (unlink(ctx_p->pidfile)) {
			FILE *pidfile;

			debug(1, "Cannot unlink pidfile \"%s\": %s. Just truncating the file.",
				ctx_p->pidfile, strerror(errno));

			SAFE ( (pidfile = fopen(ctx_p->pidfile, "w")) == NULL,	ret = errno );

			if (pidfile != NULL)
				fclose(pidfile);
		}
	}

	if (ctx_p->statusfile != NULL) {
		if (main_statusfile_f != NULL)
			if (fclose(main_statusfile_f)) {
				error("Cannot close file \"%s\".", 
					ctx_p->statusfile);
				ret = errno;
			}
		if (unlink(ctx_p->statusfile)) {
			error("Cannot unlink status file \"%s\"",
				ctx_p->statusfile);
			ret = errno;
		}
	}

	if ((!ctx_p->flags[DONTUNLINK]) && (ctx_p->listoutdir != NULL) && rm_listoutdir) {
		debug(2, "rmdir(\"%s\")", ctx_p->listoutdir);
		if (rmdir(ctx_p->listoutdir))
			error("Cannot rmdir(\"%s\")", ctx_p->listoutdir);
		if (rm_listoutdir == 2)
			free(ctx_p->listoutdir);
	}

/*
	if (ctx_p->flags[PIVOT_ROOT] == PW_AUTO || ctx_p->flags[PIVOT_ROOT] == PW_AUTORO) {
		umount2("/", MNT_DETACH);
		// DELETE THE DIRECTORY
	}
*/
	main_cleanup(ctx_p);

	if (ctx_p->watchdirwslashsize)
		free(ctx_p->watchdirwslash);

	if (ctx_p->destdirwslashsize)
		free(ctx_p->destdirwslash);

	if (ctx_p->rulfpathsize)
		free(ctx_p->rulfpath);

	error_deinit();
	ctx_cleanup(ctx_p);
	debug(1, "finished, exitcode: %i: %s.", ret, strerror(ret));
	free(ctx_p);

#ifndef __FreeBSD__	// Hanging up with 100%CPU eating, https://github.com/xaionaro/clsync/issues/97
	SAFE (posixhacks_deinit(), errno = ret = _SAFE_rc);
#endif

	return ret;
}


