/*-
 * Copyright (c) 2000-2003 Andrey Simonenko
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"

#ifndef lint
static const char rcsid[] ATTR_UNUSED =
  "@(#)$Id: ipa_cmd.c,v 1.2.2.6 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/queue.h>
#include <unistd.h>
#include <sys/types.h>
#include <grp.h>
#include <pwd.h>
#include <sys/wait.h>
#include <sys/time.h>

#ifdef HAVE_PATHS_H
# include <paths.h>
#endif
#ifndef _PATH_BSHELL
# ifdef __GNUC__
#  warning "defining _PATH_BSHELL to \"/bin/sh\""
# endif
# define _PATH_BSHELL "/bin/sh"
#endif

#include "ipa_mod.h"

#include "dlapi.h"
#include "confcommon.h"
#include "memfunc.h"
#include "parser.h"

#include "ipa_ac.h"
#include "ipa_db.h"
#include "ipa_cmd.h"
#include "ipa_ctl.h"
#include "ipa_time.h"

#include "ipa_conf.h"
#include "ipa_log.h"
#include "ipa_main.h"
#include "ipa_rules.h"
#include "ipa_autorules.h"

#ifndef WCOREDUMP
# define WCOREDUMP(x) (0)
#endif

#if defined(HAVE_CLOSEFROM) && (STDIN_FILENO > 2 || STDOUT_FILENO > 2 || STDERR_FILENO > 2)
# undef HAVE_CLOSEFROM
#endif

/*
 * exec_cmd() and exec_cmd_cons() return -1 only if they cannot fork()
 * a new process or if they get an error from waitpid(), if any error
 * occurred in a child forked from these functions, then such error
 * is invisible for exec_cmd_list*() functions.
 */

/*
 * If exec_cmd_list() function return -1, then we should
 * terminate ipa(8).
 */

/*
 * If return code from a child forked in exec_cmd_list_bg() or
 * in exec_cmd_list_fd() is non-zero then we should terminate ipa(8).
 */

ipa_mzone	*cmd_mzone;		/* Mzone for all struct cmd{}. */

ipa_mem_type	*m_cmd;			/* Memory for commands. */

char		*shell_path = NULL;	/* shell_path parameter. */
char		*shell_path_default = _PATH_BSHELL;

char		*shell_arg1 = NULL;	/* shell_arg1 parameter. */
char		*shell_arg1_default = "-c";

int		global_debug_exec;	/* global { debug_exec } */

static int	debug_exec;		/* Local modified debug_exec parameter. */

struct cmd_list	cmds_startup;		/* Standalone startup {} */
struct cmd_list	cmds_shutdown;		/* Standalone shutdown {} */

#ifdef WITH_ANY_LIMITS
struct wpid_hash wpid_hash[WPID_HASH_BUCKETS];
#endif

const char *const sync_exec_msg[] = {
	"async",
	"sync"
};

static void	printf_error(const char *, ...) ATTR_FORMAT(printf, 1, 2);
static void	printf_errorx(const char *, ...) ATTR_FORMAT(printf, 1, 2);
static void	printf_warning(const char *, ...) ATTR_FORMAT(printf, 1, 2);

/*
 * Set supplementary groups for process, gid is a GID from getpwnam(),
 * NGROUPS_MAX is defined in SUSv3.
 */
int
setsuppgids(const char *user, gid_t gid)
{
	int	i, ngids = 1;
	gid_t	gids[NGROUPS_MAX + 1];
	char	**gr_user;
	const struct group *grp;

	gids[0] = gid;

	errno = 0;
	while ( (grp = getgrent()) != NULL) {
		for (i = 0; i < ngids; ++i)
			if (grp->gr_gid == gids[i])
				goto next;
		for (gr_user = grp->gr_mem; *gr_user != NULL; ++gr_user) {
			if (strcmp(user, *gr_user) == 0) {
				if (ngids < NGROUPS_MAX + 1) {
					gids[ngids++] = grp->gr_gid;
					break;
				}
			}
		}
next:
		;
	}

	if (errno != 0) {
		xlogmsgx(IPA_LOG_ERR, "setsuppgids: getgrent: %s", strerror(errno));
		return -1;
	}

	if (setgroups(ngids, gids) < 0) {
		xlogmsgx(IPA_LOG_ERR, "setsuppgids: setgroups: %s", strerror(errno));
		return -1;
	}

	return 0;
}

/*
 * Run command changing permissions if needed, this function is
 * called with disabled log system.  Return -1 if some error occurs
 * and do not close log system in this case.
 */
static int
exec_cmd(const struct cmd *cmd)
{
	int	status;
	pid_t	childpid;

	if ( (childpid = fork()) == (pid_t)-1) {
		open_log();
		logmsg(IPA_LOG_ERR, "exec_cmd: fork");
		return -1;
	}

	if (childpid == 0) {
		/* Child. */
		const struct passwd *pwd;

		if (cmd->user != NULL) {
			errno = 0;
			if ( (pwd = getpwnam(cmd->user)) == NULL) {
				int errno_save;

				errno_save = errno;
				open_log();
				if (errno_save != 0) {
					errno = errno_save;
					logmsg(IPA_LOG_ERR, "exec_cmd: getpwnam(%s)",
					    cmd->user);
				} else
					logmsgx(IPA_LOG_ERR, "exec_cmd: cannot find user \"%s\"",
					    cmd->user);
				goto failed;
			}
			if (setgid(pwd->pw_gid) < 0) {
				open_log();
				logmsg(IPA_LOG_ERR, "exec_cmd: setgid(%lu)",
				    (u_long)pwd->pw_gid);
				goto failed;
			}
			if (setsuppgids(cmd->user, pwd->pw_gid) < 0) {
				open_log();
				logmsgx(IPA_LOG_ERR, "exec_cmd: cannot set all groups for user %s",
				    cmd->user);
				goto failed;
			}
			if (setuid(pwd->pw_uid) < 0) {
				open_log();
				logmsg(IPA_LOG_ERR, "exec_cmd: setuid(%lu)",
				    (u_long)pwd->pw_uid);
				goto failed;
			}
			/* Just, to be sure that descriptors are closed. */
			endpwent();
			endgrent();
		}
		execl(shell_path, shell_path, shell_arg1, cmd->str, (char *)NULL);
		open_log();
		logmsg(IPA_LOG_ERR, "exec_cmd: execl(\"%s\", \"%s\", \"%s\", %s) failed",
		    shell_path, shell_path, shell_arg1, parser_buf_to_string(cmd->str));
failed:
		close_log();
		_exit(127);
	}

	/* Parent. */
	while (waitpid(childpid, &status, 0) == (pid_t)-1) {
		open_log();
		if (errno != EINTR) {
			logmsg(IPA_LOG_ERR, "exec_cmd: waitpid(%ld)",
			    (long)childpid);
			return -1;
		}
		logmsg(IPA_LOG_WARNING, "exec_cmd: waitpid(%ld)",
		    (long)childpid);
		close_log();
	}

	if (WIFEXITED(status)) {
		if (WEXITSTATUS(status) != 0) {
			open_log();
			logmsgx(IPA_LOG_WARNING, "exec_cmd: exit status of child %ld (%s) is %d%s",
			    (long)childpid, parser_buf_to_string(cmd->str), WEXITSTATUS(status),
			    WEXITSTATUS(status) == 127 ? ", execution of the shell probably failed" : "");
			close_log();
		}
	} else {
		open_log();
		if (WIFSIGNALED(status))
			logmsgx(IPA_LOG_ERR, "exec_cmd: abnormal termination of child %ld (%s), signal %d%s",
			    (long)childpid, parser_buf_to_string(cmd->str),
			    WTERMSIG(status), WCOREDUMP(status) ? " (core file generated)" : "");
		else
			logmsgx(IPA_LOG_ERR, "exec_cmd: termination of child %ld (%s), unknown status",
			    (long)childpid, parser_buf_to_string(cmd->str));
		close_log();
	}

	return 0;
}

#ifndef HAVE_CLOSEFROM
static void
close_my_fd(int nofile_max)
{
	int fd;

# if STDIN_FILENO < 3 && STDOUT_FILENO < 3 && STDERR_FILENO < 3
	for (fd = 3; fd < nofile_max; ++fd)
		(void)close(fd);
# else
	for (fd = 0; fd < nofile_max; ++fd)
		switch (fd) {
		case STDIN_FILENO:
		case STDOUT_FILENO:
		case STDERR_FILENO:
			break;
		default:
			(void)close(fd);
		}
# endif
}
#endif /* !HAVE_CLOSEFROM */

/*
 * Run commands list in background and return PID of a child
 * (internal version).
 */
static pid_t
exec_cmd_list_internal(const struct cmd_list *cmd_list,
    const char *format, va_list ap)
{
	pid_t childpid;

	if ( (childpid = fork()) == (pid_t)-1) {
		logmsg(IPA_LOG_ERR, "exec_cmd_list_internal: fork");
		return (pid_t)-1;
	}

	if (childpid == 0) {
		/* Child. */
		const struct cmd *cmd;
#ifndef HAVE_CLOSEFROM
		long val;
#endif

		/* Close all descriptors, except stdin, stdout and stderr. */
#ifdef HAVE_CLOSEFROM
		close_log();
		(void)closefrom(3);
#else
		/*
		 * Since a module can call setrlimit(), it is necessary
		 * to call sysconf(_SC_OPEN_MAX) again.
		 */
		errno = 0;
		val = sysconf(_SC_OPEN_MAX);
		if (val < 0) {
			if (errno != 0)
				logmsg(IPA_LOG_ERR, "fd_init: sysconf(_SC_OPEN_MAX)");
			else
				logmsgx(IPA_LOG_ERR, "fd_init: infinite limit for _SC_OPEN_MAX is not supported");
			goto failed;
		}
# if SIZEOF_LONG > SIZEOF_INT
		if (val > INT_MAX) {
			logmsgx(IPA_LOG_ERR, "fd_init: too big value %ld for int type from sysconf(_SC_OPEN_MAX)",
			    val);
			goto failed;
		}
# endif
		close_log();
		close_my_fd((int)val);
#endif /* HAVE_CLOSEFROM */

		STAILQ_FOREACH(cmd, &cmd_list->list, link) {
			if (debug_exec > 1) {
				open_log();
				if (cmd->user == NULL)
					logmsgx(IPA_LOG_INFO, "run command: exec %s",
					    parser_buf_to_string(cmd->str));
				else
					logmsgx(IPA_LOG_INFO, "run command: exec %s %s",
					    cmd->user, parser_buf_to_string(cmd->str));
				close_log();
			}
			if (exec_cmd(cmd) < 0) {
				char *buf;

				/* Log was opened in exec_cmd(). */
				mem_vasprintf(m_tmp, &buf, format, ap);
				logmsgx(IPA_LOG_ERR, "exec_cmd_list_internal: execution of command %s from section %s failed",
				    parser_buf_to_string(cmd->str),
				    buf != NULL ? buf : "(mem_vasprintf failed)");
				goto failed;
			}
		}
		_exit(0);
failed:
		close_log();
		_exit(1);
	}

	/* Parent. */
	return childpid;
}

/*
 * Run commands list in background and wait.
 */
static int
exec_cmd_list_fg(const struct cmd_list *cmd_list, const char *format,
    va_list ap)
{
	int	status;
	pid_t	childpid;

	childpid = exec_cmd_list_internal(cmd_list, format, ap);

	if (childpid == (pid_t)-1) {
		logmsgx(IPA_LOG_ERR, "exec_cmd_list_fg: exec_cmd_list_internal failed");
		return -1;
	}

	while (waitpid(childpid, &status, 0) == (pid_t)-1) {
		if (errno != EINTR) {
			logmsg(IPA_LOG_ERR, "exec_cmd_list_fg: waitpid(%ld)",
			    (long)childpid);
			return -1;
		}
		logmsgx(IPA_LOG_WARNING, "exec_cmd_list_fg: waitpid(%ld)",
		    (long)childpid);
	}
	log_stdall();
	if (WIFEXITED(status)) {
		if (WEXITSTATUS(status) != 0) {
			logmsgx(IPA_LOG_WARNING, "exec_cmd_list_fg: exit status of child %ld, which run commands, is %d",
			    (long)childpid, WEXITSTATUS(status));
			return -1;
		}
	} else {
		if (WIFSIGNALED(status))
			logmsgx(IPA_LOG_ERR, "exec_cmd_list_fg: abnormal termination of child %ld, which run commands, signal %d%s",
			    (long)childpid, WTERMSIG(status), WCOREDUMP(status) ? " (core file generated)" : "");
		else
			logmsgx(IPA_LOG_ERR, "exec_cmd_list_fg: termination of child %ld, which run commands, unknown status",
			    (long)childpid);
		return -1;
	}

	return 0;
}

/*
 * Run commands list in background and return PID of a child.
 */
static pid_t
exec_cmd_list_bg(const struct cmd_list *cmd_list, const char *format,
    va_list ap)
{
	pid_t childpid;

	childpid = exec_cmd_list_internal(cmd_list, format, ap);

	if (childpid == (pid_t)-1)
		logmsgx(IPA_LOG_ERR, "exec_cmd_list_bg: exec_cmd_list_internal failed");

	return childpid;
}

#ifdef WITH_ANY_LIMITS

#define WPID_ARG_ATTR

void
init_wpid_hash(void)
{
	struct wpid_hash *h;

	for (h = wpid_hash; h < wpid_hash + WPID_HASH_BUCKETS; ++h)
		LIST_INIT(h);
}

int
wpid_hash_is_empty(void)
{
	const struct wpid_hash *h;

	for (h = wpid_hash; h < wpid_hash + WPID_HASH_BUCKETS; ++h)
		if (!LIST_EMPTY(h))
			return 0;
	return 1;
}

/*
 * Remove wpid structure from the hash, wpid is there
 * if its pid field is not equal to zero.
 */
void
rem_wpid_from_hash(struct wpid *wpid)
{
	if (wpid->pid != 0) {
		wpid->pid = 0;
		LIST_REMOVE(wpid, hlink);
	}
}

# define add_wpid_to_hash(w)		\
	LIST_INSERT_HEAD(&wpid_hash[get_wpid_bucket((w)->pid)], wpid, hlink)

#else

# define WPID_ARG_ATTR ATTR_UNUSED

#endif /* WITH_ANY_LIMITS */

/*
 * Run commands from cmd_list, rule and format with
 * rest arguments are used for debugging.
 */
int
exec_cmd_list(const struct rule *rule, struct wpid *wpid WPID_ARG_ATTR,
    const struct cmd_list *cmd_list, const char *format, ...)
{
	int	error = 0;
	pid_t	pid;
	va_list	ap;

	debug_exec = rule->debug_exec;

	va_start(ap, format);

	if (cmd_list->sync_exec) {
		if (exec_cmd_list_fg(cmd_list, format, ap) < 0)
			error = -1;
#ifdef WITH_ANY_LIMITS
		if (wpid != NULL)
			rem_wpid_from_hash(wpid);
#endif
	} else {
		if ( (pid = exec_cmd_list_bg(cmd_list, format, ap)) == (pid_t)-1) {
			error = -1;
#ifdef WITH_ANY_LIMITS
			if (wpid != NULL)
				rem_wpid_from_hash(wpid);
#endif
		} else {
#ifdef WITH_ANY_LIMITS
			if (wpid != NULL) {
				if (wpid->pid == 0) {
					wpid->pid = pid;
					add_wpid_to_hash(wpid);
				} else
					wpid->pid = pid;
			}
#endif
		}
	}

	va_end(ap);

	return error;
}

/*
 * Output a message to stderr and an error message if errno != 0.
 */
static void
printf_error(const char *format, ...)
{
	int	errno_save = errno;
	va_list	ap;

	va_start(ap, format);
	vlogmsgx_stderr(IPA_LOG_ERR, format, ap);
	va_end(ap);
	if (errno_save != 0)
		fprintf(stderr, "Error:  %s.\n", strerror(errno_save));
}

/*
 * Output a message to stderr.
 */
static void
printf_errorx(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	vlogmsgx_stderr(IPA_LOG_ERR, format, ap);
	va_end(ap);
}

/*
 * Output a warning message to stderr.
 */
static void
printf_warning(const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	vlogmsgx_stderr(IPA_LOG_WARNING, format, ap);
	va_end(ap);
}

/*
 * Run a command in foreground.  Return -1 if some error occurs.
 */
static int
exec_cmd_cons(const struct cmd *cmd)
{
	int	status;
	pid_t	childpid;

	if ( (childpid = fork()) == (pid_t)-1) {
		printf_error("exec_cmd_cons: fork");
		return -1;
	}

	if (childpid == 0) {
		/* Child. */
		const struct passwd *pwd;

		if (cmd->user != NULL) {
			errno = 0;
			if ( (pwd = getpwnam(cmd->user)) == NULL) {
				if (errno != 0)
					printf_error("exec_cmd_cons: getpwnam(%s)",
					    cmd->user);
				else
					printf_error("exec_cmd_cons: cannot find user \"%s\"",
					    cmd->user);
				goto failed;
			}
			if (setgid(pwd->pw_gid) < 0) {
				printf_error("exec_cmd_cons: setgid(%lu)",
				    (u_long)pwd->pw_gid);
				goto failed;
			}
			if (setsuppgids(cmd->user, pwd->pw_gid) < 0) {
				errno = 0;
				printf_error("exec_cmd_cons: cannot set all groups for user %s",
				    cmd->user);
				goto failed;
			}
			if (setuid(pwd->pw_uid) < 0) {
				printf_error("exec_cmd_cons: setuid(%lu)", (u_long)pwd->pw_uid);
				goto failed;
			}
			/* Just, to be sure that descriptors are closed. */
			endpwent();
			endgrent();
		}
		execl(shell_path, shell_path, shell_arg1, cmd->str, (char *)NULL);
		printf_error("execl(\"%s\", \"%s\", \"%s\", %s)",
		    shell_path, shell_path, shell_arg1, parser_buf_to_string(cmd->str));
failed:
		printf_errorx("child could not run this command");
		_exit(127);
	}

	/* Parent. */
	while (waitpid(childpid, &status, 0) == (pid_t)-1)
		if (errno != EINTR) {
			printf_error("exec_cmd_cons: waitpid");
			return -1;
		}

	if (WIFEXITED(status)) {
		if (WEXITSTATUS(status) != 0)
			printf_warning("exec_cmd_cons: exit status of child is %d%s",
			    WEXITSTATUS(status),
			    WEXITSTATUS(status) == 127 ? ", execution of the shell probably failed" : "");
	} else if (WIFSIGNALED(status))
		printf_warning("exec_cmd_cons: abnormal termination of child, signal %d%s",
		    WTERMSIG(status), WCOREDUMP(status) ? " (core file generated)" : "");
	else
		printf_warning("exec_cmd_cons: termination of child, unknown status");

	return 0;
}

/*
 * Run commands from cmd_list in foreground, ignoring if
 * some command can't be run, user does not exist, etc.
 */
int
exec_cmd_list_cons(const struct cmd_list *cmd_list, const char *message, ...)
{
	const struct cmd *cmd;
	va_list	ap;
#ifndef HAVE_CLOSEFROM
	long val;
#endif

	printf("Running commands from ");
	va_start(ap, message);
	vprintf(message, ap);
	va_end(ap);
	printf(" section:\n");

	if (STAILQ_EMPTY(&cmd_list->list)) {
		printf("- nothing to run\n");
		return 0;
	}

	/* Close all descriptors, except stdin, stdout and stderr. */
#ifdef HAVE_CLOSEFROM
	(void)closefrom(3);
#else
	errno = 0;
	val = sysconf(_SC_OPEN_MAX);
	if (val < 0) {
		if (errno != 0)
			printf_error("exec_cmd_list_cons: sysconf(_SC_OPEN_MAX)");
		else
			printf_errorx("exec_cmd_list_cons: infinite limit for _SC_OPEN_MAX is not supported");
		return -1;
	}
# if SIZEOF_LONG > SIZEOF_INT
	if (val > INT_MAX) {
		printf_errorx("exec_cmd_list_cons: too big value %ld for int type from sysconf(_SC_OPEN_MAX)",
		    val);
		return -1;
	}
# endif
	close_my_fd((int)val);
#endif /* HAVE_CLOSEFROM */

	STAILQ_FOREACH(cmd, &cmd_list->list, link) {
		printf("* exec ");
		if (cmd->user != NULL)
			printf("%s ", cmd->user);
		print_string(cmd->str);
		printf("\n");
		fflush(stdout);
		if (exec_cmd_cons(cmd) < 0) {
			printf_errorx("stop running commands");
			return -1;
		}
	}

	return 0;
}

/*
 * Run commands from startup (x == 0) or from shutdown (x == 1)
 * sections.
 */
int
run_cmds(int x)
{
	struct rule dummy_rule;

	dummy_rule.debug_exec = global_debug_exec;

	if (x == RC_STARTUP) {
		logmsgx(IPA_LOG_INFO, "running startup commands...");
		if (!STAILQ_EMPTY(&cmds_startup.list)) {
			if (global_debug_exec)
				logmsgx(IPA_LOG_INFO, "global startup commands: run commands (%s)",
				    sync_exec_msg[cmds_startup.sync_exec]);
			if (exec_cmd_list(&dummy_rule, (struct wpid *)NULL,
			    &cmds_startup, "startup {}") == (pid_t)-1)
				goto failed;
			free_cmd_list(&cmds_startup);
		}
	} else {
		logmsgx(IPA_LOG_INFO, "running shutdown commands...");
		if (!STAILQ_EMPTY(&cmds_shutdown.list)) {
			if (global_debug_exec)
				logmsgx(IPA_LOG_INFO, "global shutdown commands: run commands (%s)",
				    sync_exec_msg[cmds_shutdown.sync_exec]);
			if (exec_cmd_list(&dummy_rule, (struct wpid *)NULL,
			    &cmds_shutdown, "shutdown {}") == (pid_t)-1)
				goto failed;
		}
	}

	if (run_rules_cmds(x) < 0)
		goto failed;

	return 0;

failed:
	logmsgx(IPA_LOG_ERR, "stop running commands");
	return -1;
}

/*
 * Release memory used by commands list.
 */
void
free_cmd_list(struct cmd_list *cmd_list)
{
	u_int	free_mask;
	struct cmd *cmd, *cmd_next;

	for (cmd = STAILQ_FIRST(&cmd_list->list); cmd != NULL; cmd = cmd_next) {
		cmd_next = STAILQ_NEXT(cmd, link);
		free_mask = cmd->free_mask;
		if (free_mask & CMD_FREE_STR)
			mem_free(cmd->str, m_cmd);
		if (free_mask & CMD_FREE_USER)
			mem_free(cmd->user, m_cmd);
		mzone_free(cmd_mzone, cmd);
	}
	STAILQ_INIT(&cmd_list->list);
}

/*
 * Set sync_exec to -1, this means that it is undefined and
 * init head of actual list of commands.
 */
void
init_cmd_list(struct cmd_list *cmd_list)
{
	cmd_list->sync_exec = -1;
	STAILQ_INIT(&cmd_list->list);
}

/*
 * Init lists of commands in struct cmds_rule{}.
 */
void
init_cmds_rule(struct cmds_rule *ptr)
{
	ptr->set = 0;
	init_cmd_list(&ptr->cmdl);
#ifdef WITH_LIMITS
	init_cmd_list(&ptr->cmdl_if_all_reached);
	init_cmd_list(&ptr->cmdl_if_any_reached);
	init_cmd_list(&ptr->cmdl_if_all_not_reached);
	init_cmd_list(&ptr->cmdl_if_any_not_reached);
#endif
}

void
set_sync_exec_cmds_rule(struct cmds_rule *ptr)
{
#ifdef WITH_LIMITS
	int sync_exec;
#endif

	if (ptr->cmdl.sync_exec < 0)
		ptr->cmdl.sync_exec = 1;

#ifdef WITH_LIMITS
	sync_exec = ptr->cmdl.sync_exec;
	if (ptr->cmdl_if_all_reached.sync_exec < 0)
		ptr->cmdl_if_all_reached.sync_exec = sync_exec;
	if (ptr->cmdl_if_any_reached.sync_exec < 0)
		ptr->cmdl_if_any_reached.sync_exec = sync_exec;
	if (ptr->cmdl_if_all_not_reached.sync_exec < 0)
		ptr->cmdl_if_all_not_reached.sync_exec = sync_exec;
	if (ptr->cmdl_if_any_not_reached.sync_exec < 0)
		ptr->cmdl_if_any_not_reached.sync_exec = sync_exec;
#endif
}

/*
 * Release memory used by struct cmds_rule{}.
 */
void
free_cmds_rule(struct cmds_rule *ptr)
{
	free_cmd_list(&ptr->cmdl);
#ifdef WITH_LIMITS
	free_cmd_list(&ptr->cmdl_if_any_reached);
	free_cmd_list(&ptr->cmdl_if_all_reached);
	free_cmd_list(&ptr->cmdl_if_any_not_reached);
	free_cmd_list(&ptr->cmdl_if_all_not_reached);
#endif
}

#ifdef WITH_LIMITS
/*
 * Init lists of commands in struct cmds_limit{}.
 */
void
init_cmds_limit(struct cmds_limit *ptr)
{
	ptr->set = 0;
	init_cmd_list(&ptr->cmdl);
	init_cmd_list(&ptr->cmdl_if_reached);
	init_cmd_list(&ptr->cmdl_if_not_reached);
}

void
set_sync_exec_cmds_limit(struct cmds_limit *ptr)
{
	int sync_exec;

	if (ptr->cmdl.sync_exec < 0)
		ptr->cmdl.sync_exec = 1;
	sync_exec = ptr->cmdl.sync_exec;
	if (ptr->cmdl_if_reached.sync_exec < 0)
		ptr->cmdl_if_reached.sync_exec = sync_exec;
	if (ptr->cmdl_if_not_reached.sync_exec < 0)
		ptr->cmdl_if_not_reached.sync_exec = sync_exec;
}

/*
 * Release memory used by struct cmds_limit{}.
 */
void
free_cmds_limit(struct cmds_limit *ptr)
{
	free_cmd_list(&ptr->cmdl);
	free_cmd_list(&ptr->cmdl_if_reached);
	free_cmd_list(&ptr->cmdl_if_not_reached);
}
#endif /* WITH_LIMITS */

/*
 * Copy command list substituting "%..%" substrings.
 */
int
copy_cmd_list(const struct rule *rule, struct cmd_list *list_dst,
    const struct cmd_list *list_src)
{
	char	*dst;
	const char *src, *ptr;
	const char *rule_name;
	struct cmd *cmd_dst;
	const struct cmd *cmd_src;

	rule_name = rule->rule_name;

	list_dst->sync_exec = list_src->sync_exec;

	/* Copy struct cmd{} from list_src to list_dst one by one. */
	STAILQ_FOREACH(cmd_src, &list_src->list, link) {
		if ( (cmd_dst = mzone_alloc(cmd_mzone)) == NULL) {
			xlogmsgx(IPA_LOG_ERR, "copy_cmd_list: mzone_alloc failed");
			return -1;
		}
		STAILQ_INSERT_TAIL(&list_dst->list, cmd_dst, link);

		if (cmd_src->subst_per_cent == 0 && cmd_src->subst_rule == 0) {
			cmd_dst->str = cmd_src->str;
			cmd_dst->free_mask = 0;
		} else {
			if ( (cmd_dst->str = mem_malloc(cmd_src->str_size - 1 * cmd_src->subst_per_cent + (strlen(rule_name) - 6) * cmd_src->subst_rule, m_cmd)) == NULL) {
				xlogmsgx(IPA_LOG_ERR, "copy_cmd_list: mem_malloc failed");
				return -1;
			}
			/*
			 * Command string substitution algorithm:
			 *   %% -> % (that's why "- 1 * cmd_src->subst_per_cent" above)
			 *   %rule% -> rule_name ("6" above, because strlen("%rule%") == 6)
			 */
			for (src = cmd_src->str, dst = cmd_dst->str; *src != '\0'; ++src)
				if (*src == '%') {
					if (*++src == '%') {
						/* %% */
						*dst++ = '%';
					} else {
						/* %rule% */
						for (ptr = rule_name; *ptr != '\0';)
							*dst++ = *ptr++;
						src += 4; /* 4 == strlen("rule") */
					}
				} else
					*dst++ = *src;
			*dst = '\0';
			cmd_dst->free_mask = CMD_FREE_STR;
		}
		/* Don't set CMD_FREE_USER, since we share user pointer. */
		cmd_dst->user = cmd_src->user;
	}
	return 0;
}

/*
 * Copy rule's command list substituting "%...%" substrings.
 */
int
copy_cmds_rule(const struct rule *rule, struct cmds_rule *list_dst,
    const struct cmds_rule *list_src)
{
	if (copy_cmd_list(rule, &list_dst->cmdl, &list_src->cmdl) < 0
#ifdef WITH_LIMITS
	    ||
	    copy_cmd_list(rule, &list_dst->cmdl_if_all_reached, &list_src->cmdl_if_all_reached) < 0 ||
	    copy_cmd_list(rule, &list_dst->cmdl_if_all_not_reached, &list_src->cmdl_if_all_not_reached) < 0 ||
	    copy_cmd_list(rule, &list_dst->cmdl_if_any_reached, &list_src->cmdl_if_any_reached) < 0 ||
	    copy_cmd_list(rule, &list_dst->cmdl_if_any_not_reached, &list_src->cmdl_if_any_not_reached) < 0
#endif
	    ) {
		xlogmsgx(IPA_LOG_ERR, "rule %s: copy_cmds_rule: copy_cmd_list failed",
		    rule->rule_name);
		return -1;
	}
	return 0;
}

#ifdef WITH_LIMITS
/*
 * Copy limit's command list substituting "%...%" substrings.
 */
int
copy_cmds_limit(const struct rule *rule, struct cmds_limit *list_dst,
    const struct cmds_limit *list_src)
{
	if (copy_cmd_list(rule, &list_dst->cmdl, &list_src->cmdl) < 0 ||
	    copy_cmd_list(rule, &list_dst->cmdl_if_reached, &list_src->cmdl_if_reached) < 0 ||
	    copy_cmd_list(rule, &list_dst->cmdl_if_not_reached, &list_src->cmdl_if_not_reached) < 0) {
		xlogmsgx(IPA_LOG_ERR, "rule %s: copy_cmds_limit: copy_cmd_list failed",
		    rule->rule_name);
		return -1;
	}
	return 0;
}
#endif /* WITH_LIMITS */


syntax highlighted by Code2HTML, v. 0.9.1