#include <ctype.h>
#include <errno.h>
#include <pcre.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <linux/limits.h>

#include "../src/label_file.h"

static int process_file(struct saved_data *data, const char *filename)
{
	struct spec *spec;
	unsigned int line_num;
	char *line_buf = NULL;
	size_t line_len;
	ssize_t len;
	FILE *context_file;

	context_file = fopen(filename, "r");
	if (!context_file) {
		fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno));
		return -1;
	}

	line_num = 0;
	while ((len = getline(&line_buf, &line_len, context_file)) != -1) {
		char *context;
		char *mode;
		char *regex;
		char *cp, *anchored_regex;
		char *buf_p;
		pcre *re;
		pcre_extra *sd;
		const char *err;
		int items, erroff, rc;
		size_t regex_len;
		int32_t stem_id;

		len = strlen(line_buf);
		if (line_buf[len - 1] == '\n')
			line_buf[len - 1] = 0;
		buf_p = line_buf;
		while (isspace(*buf_p))
			buf_p++;
		/* Skip comment lines and empty lines. */
		if (*buf_p == '#' || *buf_p == 0)
			continue;

		items = sscanf(line_buf, "%ms %ms %ms", &regex, &mode, &context);
		if (items < 2 || items > 3) {
			fprintf(stderr, "invalid entry, skipping:%s", line_buf);
			continue;
		}

		if (items == 2) {
			context = mode;
			mode = NULL;
		}

		rc = grow_specs(data);
		if (rc) {
			fprintf(stderr, "grow_specs failed: %s\n", strerror(errno));
			return rc;
		}

		spec = &data->spec_arr[data->nspec];

		spec->lr.ctx_raw = context;
		spec->mode = string_to_mode(mode);
		if (spec->mode == -1) {
			fprintf(stderr, "%s: line %d has invalid file type %s\n",
				regex, line_num + 1, mode);
			spec->mode = 0;
		}
		free(mode);
		spec->regex_str = regex;

		stem_id = find_stem_from_spec(data, regex);
		spec->stem_id = stem_id;
		/* skip past the fixed stem part */
		if (stem_id != -1)
			regex += data->stem_arr[stem_id].len;

		regex_len = strlen(regex);
		cp = anchored_regex = malloc(regex_len + 3);
		if (!cp) {
			fprintf(stderr, "Malloc Failed: %s\n", strerror(errno));
			return -1;
		}
		*cp++ = '^';
		memcpy(cp, regex, regex_len);
		cp += regex_len;
		*cp++ = '$';
		*cp = '\0';

		spec_hasMetaChars(spec);

		re = pcre_compile(anchored_regex, 0, &err, &erroff, NULL);
		if (!re) {
			fprintf(stderr, "PCRE compilation failed for %s at offset %d: %s\n", anchored_regex, erroff, err);
			return -1;
		}
		spec->regex = re;

		sd = pcre_study(re, 0, &err);
		if (!sd) {
			fprintf(stderr, "PCRE study failed for %s: %s\n", anchored_regex, err);
			return -1;
		}
		free(anchored_regex);
		spec->sd = sd;

		line_num++;
		data->nspec++;
	}

	free(line_buf);
	fclose(context_file);

	return 0;
}

/*
 * File Format
 *
 * u32 - magic number
 * u32 - version
 * u32 - number of stems
 * ** Stems
 * 	u32  - length of stem EXCLUDING nul
 * 	char - stem char array INCLUDING nul
 * u32 - number of regexs
 * ** Regexes
 * 	u32  - length of upcoming context INCLUDING nul
 * 	char - char array of the raw context
 *	u32  - length of the upcoming regex_str
 *	char - char array of the original regex string including the stem.
 *	mode_t - mode bits
 *	s32  - stemid associated with the regex
 *	u32  - spec has meta characters
 *	u32  - data length of the pcre regex
 *	char - a bufer holding the raw pcre regex info
 *	u32  - data length of the pcre regex study daya
 *	char - a buffer holding the raw pcre regex study data
 */
static int write_binary_file(struct saved_data *data, char *filename)
{
	struct spec *specs = data->spec_arr;
	FILE *bin_file;
	size_t len;
	uint32_t magic = SELINUX_MAGIC_COMPILED_FCONTEXT;
	uint32_t section_len;
	uint32_t i;
	int rc;

	bin_file = fopen(filename, "w");
	if (!bin_file) {
		perror("fopen output_file");
		exit(EXIT_FAILURE);
	}

	/* write some magic number */
	len = fwrite(&magic, sizeof(uint32_t), 1, bin_file);
	if (len != 1)
		goto err;

	/* write the version */
	section_len = SELINUX_COMPILED_FCONTEXT_MAX_VERS;
	len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
	if (len != 1)
		goto err;

	/* write the number of stems coming */
	section_len = data->num_stems;
	len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
	if (len != 1)
		goto err;

	for (i = 0; i < section_len; i++) {
		char *stem = data->stem_arr[i].buf;
		uint32_t stem_len = data->stem_arr[i].len;

		/* write the strlen (aka no nul) */
		len = fwrite(&stem_len, sizeof(uint32_t), 1, bin_file);
		if (len != 1)
			goto err;

		/* include the nul in the file */
		stem_len += 1;
		len = fwrite(stem, sizeof(char), stem_len, bin_file);
		if (len != stem_len)
			goto err;
	}

	/* write the number of regexes coming */
	section_len = data->nspec;
	len = fwrite(&section_len, sizeof(uint32_t), 1, bin_file);
	if (len != 1)
		goto err;

	for (i = 0; i < section_len; i++) {
		char *context = specs[i].lr.ctx_raw;
		char *regex_str = specs[i].regex_str;
		mode_t mode = specs[i].mode;
		int32_t stem_id = specs[i].stem_id;
		pcre *re = specs[i].regex;
		pcre_extra *sd = get_pcre_extra(&specs[i]);
		uint32_t to_write;
		size_t size;

		/* length of the context string (including nul) */
		to_write = strlen(context) + 1;
		len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
		if (len != 1)
			goto err;

		/* original context strin (including nul) */
		len = fwrite(context, sizeof(char), to_write, bin_file);
		if (len != to_write)
			goto err;

		/* length of the original regex string (including nul) */
		to_write = strlen(regex_str) + 1;
		len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
		if (len != 1)
			goto err;

		/* original regex string */
		len = fwrite(regex_str, sizeof(char), to_write, bin_file);
		if (len != to_write)
			goto err;

		/* binary F_MODE bits */
		len = fwrite(&mode, sizeof(mode), 1, bin_file);
		if (len != 1)
			goto err;

		/* stem for this regex (could be -1) */
		len = fwrite(&stem_id, sizeof(stem_id), 1, bin_file);
		if (len != 1)
			goto err;

		/* does this spec have a metaChar? */
		to_write = specs[i].hasMetaChars;
		len = fwrite(&to_write, sizeof(to_write), 1, bin_file);
		if (len != 1)
			goto err;

		/* determine the size of the pcre data in bytes */
		rc = pcre_fullinfo(re, NULL, PCRE_INFO_SIZE, &size);
		if (rc < 0)
			goto err;

		/* write the number of bytes in the pcre data */
		to_write = size;
		len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
		if (len != 1)
			goto err;

		/* write the actual pcre data as a char array */
		len = fwrite(re, 1, to_write, bin_file);
		if (len != to_write)
			goto err;

		/* determine the size of the pcre study info */
		rc = pcre_fullinfo(re, sd, PCRE_INFO_STUDYSIZE, &size);
		if (rc < 0)
			goto err;

		/* write the number of bytes in the pcre study data */
		to_write = size;
		len = fwrite(&to_write, sizeof(uint32_t), 1, bin_file);
		if (len != 1)
			goto err;

		/* write the actual pcre study data as a char array */
		len = fwrite(sd->study_data, 1, to_write, bin_file);
		if (len != to_write)
			goto err;
	}

	rc = 0;
out:
	fclose(bin_file);
	return rc;
err:
	rc = -1;
	goto out;
}

static int free_specs(struct saved_data *data)
{
	struct spec *specs = data->spec_arr;
	unsigned int num_entries = data->nspec;
	unsigned int i;

	for (i = 0; i < num_entries; i++) {
		free(specs[i].lr.ctx_raw);
		free(specs[i].lr.ctx_trans);
		free(specs[i].regex_str);
		pcre_free(specs[i].regex);
		pcre_free_study(specs[i].sd);
	}
	free(specs);

	num_entries = data->num_stems;
	for (i = 0; i < num_entries; i++) {
		free(data->stem_arr[i].buf);
	}
	free(data->stem_arr);

	memset(data, 0, sizeof(*data));
	return 0;
}

int main(int argc, char *argv[])
{
	struct saved_data data;
	const char *path;
	char stack_path[PATH_MAX + 1];
	int rc;

	if (argc != 2) {
		fprintf(stderr, "usage: %s input_file\n", argv[0]);
		exit(EXIT_FAILURE);
	}

	memset(&data, 0, sizeof(data));

	path = argv[1];

	rc = process_file(&data, path);
	if (rc < 0)
		return rc;

	rc = sort_specs(&data);
	if (rc)
		return rc;

	rc = snprintf(stack_path, sizeof(stack_path), "%s.bin", path);
	if (rc < 0 || rc >= sizeof(stack_path))
		return rc;
	rc = write_binary_file(&data, stack_path);
	if (rc < 0)
		return rc;

	rc = free_specs(&data);
	if (rc < 0)
		return rc;

	return 0;
}
