#define _GNU_SOURCE
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "kexec.h"
#include "crashdump.h"

/*
 * kexec_iomem_for_each_line()
 *
 * Iterate over each line in the file returned by proc_iomem(). If match is
 * NULL or if the line matches with our match-pattern then call the
 * callback if non-NULL.
 * If match is NULL, callback should return a negative if error.
 * Otherwise the interation goes on, incrementing nr but only if callback
 * returns 0 (matched).
 *
 * Return the number of lines matched.
 */

int kexec_iomem_for_each_line(char *match,
			      int (*callback)(void *data,
					      int nr,
					      char *str,
					      unsigned long long base,
					      unsigned long long length),
			      void *data)
{
	const char *iomem = proc_iomem();
	char line[MAX_LINE];
	FILE *fp;
	unsigned long long start, end, size;
	char *str;
	int consumed;
	int count;
	int nr = 0, ret;

	fp = fopen(iomem, "r");
	if (!fp)
		die("Cannot open %s\n", iomem);

	while(fgets(line, sizeof(line), fp) != 0) {
		count = sscanf(line, "%llx-%llx : %n", &start, &end, &consumed);
		if (count != 2)
			continue;
		str = line + consumed;
		size = end - start + 1;
		if (!match || memcmp(str, match, strlen(match)) == 0) {
			if (callback) {
				ret = callback(data, nr, str, start, size);
				if (ret < 0)
					break;
				else if (ret == 0)
					nr++;
			}
		}
	}

	fclose(fp);

	return nr;
}

static int kexec_iomem_single_callback(void *data, int nr,
				       char *UNUSED(str),
				       unsigned long long base,
				       unsigned long long length)
{
	struct memory_range *range = data;

	if (nr == 0) {
		range->start = base;
		range->end = base + length - 1;
	}

	return 0;
}

int parse_iomem_single(char *str, uint64_t *start, uint64_t *end)
{
	struct memory_range range;
	int ret;

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

	ret = kexec_iomem_for_each_line(str,
	                                kexec_iomem_single_callback, &range);

	if (ret == 1) {
		if (start)
			*start = range.start;
		if (end)
			*end = range.end;

		ret = 0;
	}
	else
		ret = -1;

	return ret;
}
