/*
 * Copyright (c) 2008,2009 by Dmitry V. Levin.  See LICENSE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>

#include "passwdqc.h"
#include "concat.h"

static char *mkreason(const char *what, const char *pathname,
    unsigned int lineno, const char *why)
{
	char buf[sizeof(unsigned int) * 3 + 1];
	const char *at_line = (lineno ? " at line " : "");
	const char *at_num = (lineno ? buf : "");

	if (lineno)
		sprintf(buf, "%u", lineno);
	return concat(what, " \"", pathname, "\"", at_line, at_num, ": ",
	    (why ? why : strerror(errno)), NULL);
}

static char *
skip_whitespaces(char *str)
{
	char *p;

	for (p = str; *p == ' ' || *p == '\t' || *p == '\r' || *p == '\n'; ++p)
		;
	return p;
}

static char *
skip_nonwhitespaces(char *str)
{
	char *p;

	for (p = str;
	    *p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n'; ++p)
		;
	return p;
}

static int
parse_file(FILE *fp, passwdqc_params_t *params, char **reason,
    const char *pathname)
{
	unsigned int lineno;
	char buf[8192];

	for (lineno = 1; fgets(buf, sizeof(buf), fp); ++lineno) {
		char *str, *end, *rt;
		const char *cstr;
		int rc;

		if (strlen(buf) >= sizeof(buf) - 1) {
			*reason = mkreason("Error reading", pathname,
			    lineno, "Line too long");
			return -1;
		}

		str = skip_whitespaces(buf);
		if (!*str || *str == '#')
			continue;

		end = skip_nonwhitespaces(str);
		if (*skip_whitespaces(end)) {
			*reason = mkreason("Error loading", pathname,
			    lineno, "Unexpected token");
			return -1;
		}
		*end = '\0';

		cstr = str;
		if ((rc = passwdqc_params_parse(params, &rt, 1, &cstr))) {
			*reason = mkreason("Error loading", pathname,
			    lineno, (rt ? rt : "Out of memory"));
			free(rt);
			return rc;
		}
	}

	if (!feof(fp) || ferror(fp)) {
		*reason = mkreason("Error reading", pathname, 0, NULL);
		return -1;
	}

	return 0;
}

struct dev_ino_t;
struct dev_ino_t {
	struct dev_ino_t *next;
	dev_t dev;
	ino_t ino;
};

static struct dev_ino_t *dev_ino_head;

int
passwdqc_params_load(passwdqc_params_t *params, char **reason,
    const char *pathname)
{
	int rc;
	FILE *fp;
	struct dev_ino_t di, *di_p;
	struct stat st;

	if (!(fp = fopen(pathname, "r"))) {
		*reason = mkreason("Error opening", pathname, 0, NULL);
		return -1;
	}

	if (fstat(fileno(fp), &st)) {
		*reason = mkreason("Error stat", pathname, 0, NULL);
		fclose(fp);
		return -1;
	}

	di.dev = st.st_dev;
	di.ino = st.st_ino;
	for (di_p = dev_ino_head; di_p; di_p = di_p->next)
		if (di_p->dev == di.dev && di_p->ino == di.ino)
			break;
	if (di_p) {
		*reason = mkreason("Error opening", pathname, 0,
		    "Loop detected");
		fclose(fp);
		return -1;
	}

	di.next = dev_ino_head;
	dev_ino_head = &di;

	rc = parse_file(fp, params, reason, pathname);
	fclose(fp);

	dev_ino_head = dev_ino_head->next;
	return rc;
}