/*
 * Routines for parsing /proc/devices for use by the ioctl fuzzer.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/kdev_t.h>
#include "files.h"
#include "trinity.h"


static struct {
	int major;
	int minor;
	const char *name;
} *block_devs, *char_devs, *misc_devs;

static size_t bldevs, chrdevs, miscdevs;

static void parse_proc_devices(void)
{
	FILE *fp;
	char *p, *name, *line = NULL;
	size_t n = 0;
	int block, major;
	void *new;

	fp = fopen("/proc/devices", "r");
	if (!fp)
		return;

	block = 0;

	while (getline(&line, &n, fp) >= 0) {
		if (strcmp("Block devices:\n", line) == 0)
			block = 1;
		else if (strcmp("Character devices:\n", line) == 0)
			block = 0;
		else if (sscanf(line, "%d %*s", &major) == 1) {
			if ((p = strchr(line, '\n')) != NULL)
				*p = 0;
			if ((p = strrchr(line, ' ')) == NULL)
				continue;
			p++;
			name = strdup(p);

			if (block) {
				new = realloc(block_devs, (bldevs+1)*sizeof(*block_devs));
				if (!new) {	// FIXME: We should propagate failure up here.
					free(name);
					break;
				}
				block_devs = new;
				block_devs[bldevs].major = major;
				block_devs[bldevs].minor = 0;
				block_devs[bldevs].name = name;
				bldevs++;
			} else {
				new = realloc(char_devs, (chrdevs+1)*sizeof(*char_devs));
				if (!new) {
					free(name);
					break;
				}
				char_devs = new;
				char_devs[chrdevs].major = major;
				char_devs[chrdevs].minor = 0;
				char_devs[chrdevs].name = name;
				chrdevs++;
			}
		}
	}

	fclose(fp);
	free(line);
}

static void parse_proc_misc(void)
{
	FILE *fp;
	char *name;
	int minor;
	void *new;

	fp = fopen("/proc/misc", "r");
	if (!fp)
		return;

	while (fscanf(fp, "%d %ms", &minor, &name) == 2) {
		new = realloc(misc_devs, (miscdevs+1)*sizeof(*misc_devs));
		if (!new) {
			free(name);
			break;
		}
		misc_devs = new;
		misc_devs[miscdevs].major = 0;
		misc_devs[miscdevs].minor = minor;
		misc_devs[miscdevs].name = name;
		miscdevs++;
	}

	fclose(fp);
}

void parse_devices(void)
{
	parse_proc_devices();
	parse_proc_misc();

	output(2, "Parsed %zu char devices, %zu block devices, %zu misc devices.\n",
			chrdevs, bldevs, miscdevs);
}

const char *map_dev(dev_t st_rdev, mode_t st_mode)
{
	int major, minor;
	size_t i;

	major = MAJOR(st_rdev);
	minor = MINOR(st_rdev);

	if (S_ISCHR(st_mode)) {
		for (i = 0; i < chrdevs; ++i) {
			if (char_devs[i].major == major) {
				if (strcmp(char_devs[i].name, "misc") == 0) {
					size_t j;

					for (j = 0; j < miscdevs; ++j)
						if (misc_devs[j].minor == minor)
							return misc_devs[j].name;
				} else
					return char_devs[i].name;
			}
		}
	} else if (S_ISBLK(st_mode)) {
		for (i = 0; i < bldevs; ++i) {
			if (block_devs[i].major == major)
				return block_devs[i].name;
		}
	}

	return NULL;
}
