/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*-
 *
 * This file is part of PRoot.
 *
 * Copyright (C) 2014 STMicroelectronics
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

#ifndef EXTENSION_H
#define EXTENSION_H

#include <sys/queue.h> /* LIST_, */
#include <stdint.h>    /* intptr_t, */
#include <stdbool.h>   /* bool, */

#include "tracee/tracee.h"
#include "syscall/seccomp.h"

/* List of possible events.  */
typedef enum {
	/* A guest path passed as an argument of the current syscall
	 * is about to be translated: "(char *) data1" is the base for
	 * "(char *) data2" -- the guest path -- if this latter is
	 * relative.  If the extension returns > 0, then PRoot skips
	 * its own handling.  If the extension returns < 0, then PRoot
	 * reports this errno as-is.  */
	GUEST_PATH,

	/* A canonicalized host path is being accessed during the
	 * translation of a guest path: "(char *) data1" is the
	 * canonicalized host path and "(bool) data2" is true if it is
	 * the final path.  Note that several host paths are accessed
	 * for a given guest path since PRoot has to walk along all
	 * parent directories and symlinks in order to translate it.
	 * If the extension returns < 0, then PRoot reports this errno
	 * as-is.  */
	HOST_PATH,

	/* The tracee enters a syscall, and PRoot hasn't do anything
	 * yet.  If the extension returns > 0, then PRoot skips its
	 * own handling.  If the extension returns < 0, then PRoot
	 * cancels the syscall and reports this errno to the
	 * tracee.  */
	SYSCALL_ENTER_START,

	/* The tracee enters a syscall, and PRoot has already handled
	 * it: "(int) data1" is the current status, it is < 0 when
	 * something went wrong.  If the extension returns < 0, then
	 * PRoot cancels the syscall and reports this errno to the
	 * tracee.  */
	SYSCALL_ENTER_END,

	/* The tracee exits a syscall, and PRoot hasn't do anything
	 * yet.  If the extension returns > 0, then PRoot skips its
	 * own handling.  If the extension returns < 0, then PRoot
	 * reports this errno to the tracee.  */
	SYSCALL_EXIT_START,

	/* The tracee exits a syscall, and PRoot has already handled
	 * it.  If the extension returns < 0, then PRoot reports this
	 * errno to the tracee.  */
	SYSCALL_EXIT_END,

	/* The tracee is stopped either because of a syscall or a
	 * signal: "(int) data1" is its new status as reported by
	 * waitpid(2).  If the extension returns != 0, then PRoot
	 * skips its own handling.  */
	NEW_STATUS,

	/* Ask how this extension is inheritable: "(Tracee *) data1"
	 * is the child tracee and "(bool) data2" is the clone(2)
	 * flags (CLONE_RECONF for sub-reconfiguration).  The meaning
	 * of the returned value is:
	 *
	 *   < 0 : not inheritable
	 *  == 0 : inheritable + shared configuration.
	 *   > 0 : inheritable + call INHERIT_CHILD.  */
	INHERIT_PARENT,

	/* Control the inheritance: "(Extension *) data1" is the
	 * extension of the parent and "(word_t) data2" is the clone(2)
	 * flags (CLONE_RECONF for sub-reconfiguration).  For instance
	 * the extension for the child could use a configuration
	 * different from the parent's configuration.  */
	INHERIT_CHILD,

	/* The tracee enters a "chained" syscall, that is, an
	 * unrequested syscall inserted by PRoot after an actual
	 * syscall.  If the extension returns < 0, then PRoot cancels
	 * the syscall and reports this errno to the tracee.  */
	SYSCALL_CHAINED_ENTER,

	/* The tracee exists a "chained" syscall, that is, an
	 * unrequested syscall inserted by PRoot after an actual
	 * syscall.  */
	SYSCALL_CHAINED_EXIT,

	/* Initialize the extension: "(const char *) data1" is its
	 * argument that was passed to the command-line interface.  If
	 * the extension returns < 0, then PRoot removed it.  */
	INITIALIZATION,

	/* The extension is not attached to its tracee anymore
	 * (destructor).  */
	REMOVED,

	/* Print the current configuration of the extension.  See
	 * print_config() as an example.  */
	PRINT_CONFIG,

	/* Print the usage of the extension: "(bool) data1" is true
	 * for a detailed usage.  See print_usage() as an example.  */
	PRINT_USAGE,
} ExtensionEvent;

#define CLONE_RECONF ((word_t) -1)

struct extension;
typedef int (*extension_callback_t)(struct extension *extension, ExtensionEvent event,
				intptr_t data1, intptr_t data2);

typedef struct extension {
	/* Function to be called when any event occured.  */
	extension_callback_t callback;

	/* A chunk of memory allocated by any talloc functions.
	 * Mainly useful to store a configuration.  */
	TALLOC_CTX *config;

	/* List of sysnum handled by this extension.  */
	const FilteredSysnum *filtered_sysnums;

	/* Link to the next and previous extensions.  Note the order
	 * is *never* garantee.  */
	LIST_ENTRY(extension) link;
} Extension;

typedef LIST_HEAD(extensions, extension) Extensions;

extern int initialize_extension(Tracee *tracee, extension_callback_t callback, const char *cli);
extern void inherit_extensions(Tracee *child, Tracee *parent, word_t clone_flags);

/**
 * Notify all extensions of @tracee that the given @event occured.
 * See ExtensionEvent for the meaning of @data1 and @data2.
 */
static inline int notify_extensions(Tracee *tracee, ExtensionEvent event,
				intptr_t data1, intptr_t data2)
{
	Extension *extension;

	if (tracee->extensions == NULL)
		return 0;

	LIST_FOREACH(extension, tracee->extensions, link) {
		int status = extension->callback(extension, event, data1, data2);
		if (status != 0)
			return status;
	}

	return 0;
}

/* Built-in extensions.  */
extern int kompat_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2);
extern int fake_id0_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2);
extern int care_callback(Extension *extension, ExtensionEvent event, intptr_t d1, intptr_t d2);

#endif /* EXTENSION_H */
