/*
* mdev - implement a small udev
* Copyright Frank Sorenson <frank@tuxrocks.com> 2005
*
* Permission is hereby granted to copy, modify and redistribute this code
* in terms of the GNU Library General Public License, Version 2 or later,
* at your option.
*
*/

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/socket.h>
#include <sys/poll.h>

#include <linux/types.h>
#include <linux/netlink.h>

#define DEV_PATH	"/tmp/dev"
#define DEV_MODE	0700
#define BUFFER_LENGTH	512

#ifndef NETLINK_KOBJECT_UEVENT
#define NETLINK_KOBJECT_UEVENT 15
#endif

#ifndef SO_RCVBUFFORCE
#define SO_RCVBUFFORCE 33
#endif

int quit = 0;

char string_cmpn(const char *s1, const char *s2, int count)
{
	int n = 0;

	while (s1[n] == s2[n]) {
		if (s1[n] == '\0')
			return 1;
		n++;
		if (count == n)
			return 1;
	}
	return 0;
}

/* s1: string; s2: string; append s2 onto the end of s1 */
char string_cat(char *s1, const char *s2)
{
	int n = 0;
	char *sp1;

	while (s1[n] != '\0')
		n++;
	sp1 = s1 + n;
	n = 0;
	while (s2[n] != '\0') {
		sp1[n] = s2[n];
		n++;
	}
	return n;
}

/* similar to memset 0 */
void clear_mem(void *_addr, int size)
{
	unsigned char *addr = _addr;

	size--;
	while (size >= 0) {
		addr[size--] = 0;
	}
}


int file_exists(const char *file_name)
{
	int res;
	struct stat stat_buf;

	res = stat(file_name, &stat_buf);
	if (res == -1) {
		switch (errno) {
			case ENOENT:
			case ENOTDIR:
				return 0;
				break;
			default:
				break;
		}
	}
	return 1;
}

int get_device_info(const char *path, int *major, int *minor)
{
	char buf[BUFFER_LENGTH];
	int fd;
	int ret;
	char *chr;
	int tempval = 0;

	sprintf(buf, "%s/dev", path);
	fd = open(buf, O_RDONLY);
	ret = read(fd, buf, BUFFER_LENGTH - 1);
	if (ret == 0)
		return -1;
	close(fd);
	buf[ret - 1] = '\0';

	chr = buf;
	while (*chr != '\0') {
		if (*chr == ':') {
			*major = tempval;
			tempval = 0;
		} else {
			tempval *= 10;
			tempval += (*chr - '0');
		}
		chr ++;
	}
	*minor = tempval;

	return 0;
}

int make_device(const char *path)
{
	char buf[BUFFER_LENGTH];
	const char *device_name;
	int major;
	int minor;
	int ret;
	int local_errno;
	int type;

	device_name = path;
	while (*device_name != '\0')
		device_name ++;
	while ((*(device_name - 1) != '/') && (device_name > path))
		device_name --;
	get_device_info(path, &major, &minor);
/*	printf("%s: %d %d\n", device_name, major, minor); */

	sprintf(buf, "%s/%s", DEV_PATH, device_name);
	if (string_cmpn(path, "/sys/block", 10))
		type = S_IFBLK;
	else if (string_cmpn(path, "/sys/class", 10))
		type = S_IFCHR;
	else {
		printf("Cannot determing type of %s\n", buf);
		return -1;
	}
	ret = mknod(buf, DEV_MODE | type, makedev(major, minor));
	if (ret != 0) {
		local_errno = errno;
		if (local_errno != EEXIST)
			printf("Could not create device node %s: error %d (%s)\n", buf, local_errno, strerror(local_errno));
	}

	return 0;
}

int find_dev(const char *path)
{
	DIR *dir;
	struct dirent *entry;
	char temp_path[BUFFER_LENGTH];
	int local_errno;

	dir = opendir(path);
	if (dir == NULL) {
		printf("Could not open path %s: error %d (%s)\n", path, local_errno, strerror(local_errno));
		exit(-1);
	}

	do {
		entry = readdir(dir);
		if (entry == NULL)
			break;
		if (string_cmpn(entry->d_name, ".", BUFFER_LENGTH))
			continue;
		if (string_cmpn(entry->d_name, "..", BUFFER_LENGTH))
			continue;
		if (entry->d_type == DT_DIR) {
			sprintf(temp_path, "%s/%s", path, entry->d_name);
			find_dev(temp_path);
		}
		if (string_cmpn(entry->d_name, "dev", BUFFER_LENGTH))
			make_device(path);
	} while (entry != NULL);
	closedir(dir);

	return 0;
}

int initialize_netlink_sock()
{
	struct sockaddr_nl sockaddr_netlink;
	const int buffersize = 16*1024*1024;
	int netlink_sock;
	int ret;

	clear_mem(&sockaddr_netlink, sizeof(struct sockaddr_nl));
	sockaddr_netlink.nl_family = AF_NETLINK;
	sockaddr_netlink.nl_pid = getpid();
	sockaddr_netlink.nl_groups = 0xffffffff;

	netlink_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
	if (netlink_sock == -1) {
		printf("Couldn't open netlink socket: %s", strerror(errno));
		return -1;
	}

	setsockopt(netlink_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize));

	ret = bind(netlink_sock, (struct sockaddr *) &sockaddr_netlink, sizeof(struct sockaddr_nl));
	if (ret < 0) {
		printf("Couldn't bind to netlink socket: %s", strerror(errno));
		return -1;
	}
	return netlink_sock;
}

int process_netlink_message(char *message)
{
	char *ptr;
	char buf[BUFFER_LENGTH];

	if (string_cmpn(message, "add@/class/", 11) || string_cmpn(message, "add@/block/", 11)) {
		message[0] = '/';
		message[1] = 's';
		message[2] = 'y';
		message[3] = 's';
		make_device(message);
	} else if (string_cmpn(message, "remove@/class/", 14) || string_cmpn(message, "remove@/block/", 14)) {
		ptr = message;

		while (*ptr != '\0')
			ptr ++;
		while ((*(ptr - 1) != '/') && (ptr > message))
			ptr --;
		sprintf(buf, "%s/%s", DEV_PATH, ptr);
		if (file_exists(buf)) {
/*			printf("Removing %s\n", buf); */
			unlink(buf);
		}
	} else if (string_cmpn(message, "add@/module/", 12) || string_cmpn(message, "remove@/module/", 15)) {
		/* ignore module add/remove messages */
	} else
		printf("Unknown message: %s\n", message);

	return 0;
}

int run_daemon()
{
	int netlink_sock;
	struct pollfd fd;
	int ret;
	char buffer[BUFFER_LENGTH];

	netlink_sock = initialize_netlink_sock();
	if (netlink_sock <= 0)
		return -1;

	fd.fd = netlink_sock;
	fd.events |= POLLIN;

	while (!quit) {
		ret = poll(&fd, 1, 100);
		if (ret == -1)
			break;
		ret = recv(netlink_sock, &buffer, sizeof(buffer), 0);
		process_netlink_message(buffer);
	}
	return 0;
}

int usage(int argc, char *argv[])
{
	printf("%s [ -s | -d ]", argv[0]);
	printf("    -s run through /sys and create needed device nodes\n");
	printf("    -d run as a daemon and add/remove device nodes as needed\n");

	return 0;
}

int main(int argc, char *argv[])
{
	int populate = 0;
	int daemonize = 0;
	int n;

	for (n = 1 ; n < argc ; n ++) {
		if (string_cmpn(argv[n], "-s", BUFFER_LENGTH))
			populate = 1;
		else if (string_cmpn(argv[n], "-d", BUFFER_LENGTH))
			daemonize = 1;
		else {
			printf("Unknown argument: %s\n", argv[n]);
			usage(argc, argv);
			exit(-1);
		}
	}

	if (populate)
		find_dev("/sys");

	if (daemonize)
		run_daemon();

	return 0;
}


