/*
 *   Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007
 *   NOVELL (All rights reserved)
 *   Copyright (c) 2010 - 2012
 *   Canonical Ltd.
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of version 2 of the GNU General Public
 *   License published by the Free Software Foundation.
 *
 *   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, contact Canonical, Ltd.
 */

/* Handle apparmor includes, as a straight forward preprocessing phase.
   While we are at it we will strip comments.  Why? because it made it
   easier.

   We support 2 types of includes

#include <name> which searches for the first occurrence of name in the
   apparmor directory path.

#include "name" which will search for a relative or absolute pathed
   file

-p : preprocess only.  Dump output to stdout
-I path : add a path to be search by #include < >
-b path : set the base path to something other than /etc/apparmor.d

*/

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <limits.h>

#include "lib.h"
#include "parser.h"
#include "parser_include.h"

/* An array of search directories, I sure hope 100's enough */
#define MAX_PATH 100

/* maximum depth of nesting */
#define MAX_NEST_LEVEL 100

static char *path[MAX_PATH] = { NULL };
static int npath = 0;

/* default base directory is /etc/apparmor.d, it can be overridden
   with the -b option. */

const char *basedir;
static const char *default_basedir = "/etc/apparmor.d";


/* set up basedir so that it can be overridden/used later. */
void init_base_dir(void)
{
	int rc;
	struct stat sbuf;

	/* basedir should always start out NULL; if not, something's
	 * wrong.*/
	assert(basedir == NULL);

	rc = stat(default_basedir, &sbuf);
	if (rc == 0 && S_ISDIR(sbuf.st_mode)) {
		basedir = default_basedir;
		return;
	}
}

/* Set the base dir.  Used to change default path for relative includes */
void set_base_dir(char *dir)
{
	char *t;
	int i, rc;
	struct stat sbuf;

	t = strndup(dir, PATH_MAX);
	if (!t) {
		PERROR(_("Error: Out of memory.\n"));
		return;
	}

	/*strip trailing /'s */
	for (i = strlen(t) - 1; i >= 0 && t[i] == '/'; i--)
		t[i] = 0;

	rc = stat(t, &sbuf);
	if (rc == -1 || !S_ISDIR(sbuf.st_mode)) {
		PERROR(_("Error: basedir %s is not a directory, skipping.\n"), t);
		free(t);
		return;
	}

	basedir = t;
	return;
}

/* Add a directory to the search path. */
int add_search_dir(const char *dir)
{
	char *t;
	size_t len;

	if (npath >= MAX_PATH) {
		PERROR(_("Error: Could not add directory %s to search path.\n"),
		       dir);
		return 0;
	}

	if (!dir)
		return 1;

	len = strlen(dir);
	if (len == 0)
		return 1;

	t = strdup(dir);
	if (t == NULL) {
		PERROR(_("Error: Could not allocate memory.\n"));
		return 0;
	}

	/*strip trailing /'s */
	while (len > 0 && t[--len] == '/')
		t[len] = '\0';
	path[npath] = t;
	npath++;

	return 1;
}

void parse_default_paths(void)
{
	add_search_dir(basedir);
}

FILE *search_path(char *filename, char **fullpath, bool *skip)
{
	FILE *newf = NULL;
	char *buf = NULL;
	int i;
	for (i = 0; i < npath; i++) {
		if (asprintf(&buf, "%s/%s", path[i], filename) < 0) {
			perror("asprintf");
			exit(1);
		}

		if (g_includecache->find(buf)) {
			/* hit do not want to re-include */
			*skip = true;
			free(buf);
			return NULL;
		}

		newf = fopen(buf, "r");
		if (newf) {
			/* ignore failing to insert into cache */
			(void) g_includecache->insert(buf);
			if (fullpath)
				*fullpath = buf;
			else
				free(buf);
			break;
		}
		free(buf);
		buf = NULL;
	}
	*skip = false;
	return newf;
}

struct include_stack_t {
	char *filename;
	int lineno;
	struct include_stack_t *next;
};

struct include_stack_t *include_stack_head = NULL;

static void start_include_position(const char *filename)
{
	if (current_filename)
		free(current_filename);
	current_filename = strdup(filename ? filename : "stdin");
	current_lineno   = 1;
}

void push_include_stack(char *filename)
{
	struct include_stack_t *include = NULL;

	include = (struct include_stack_t *) malloc(sizeof(*include));
	if (!include) {
		perror("malloc of included file stack tracker");
		/* failures in this area are non-fatal */
		return;
	}

	include->filename  = strdup(current_filename);
	include->lineno    = current_lineno;
	include->next      = include_stack_head;
	include_stack_head = include;

	start_include_position(filename);
}

void pop_include_stack(void)
{
	struct include_stack_t *include = NULL;

	if (!include_stack_head)
		return;

	include = include_stack_head;
	include_stack_head = include->next;

	if (current_filename)
		free(current_filename);
	current_filename = include->filename;
	current_lineno   = include->lineno;
	free(include);
}

void reset_include_stack(const char *filename)
{
	while (include_stack_head)
		pop_include_stack();

	start_include_position(filename);
}
