/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * qudevmon.c
 * Copyright (C) Qumbetlian 2007 <qumbetlian@yahoo.com>
 * 
 * qudevmon.c is free software.
 * 
 * You may redistribute it and/or modify it under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 * 
 * qudevmon.c is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with USB-monitor.c.  If not, write to:
 * 	The Free Software Foundation, Inc.,
 * 	51 Franklin Street, Fifth Floor
 * 	Boston, MA  02110-1301, USA.
 *
 * This program was inspired by udevmonitor.c - copyright (C) 2004-2006 Kay Sievers
 * <kay.sievers@vrfy.org>
 *
 */

#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <linux/linkage.h>

#define INTERPRETERPATH "/bin/sh"
#define UEVENT_BUFFER_SIZE		2048
#ifndef NETLINK_KOBJECT_UEVENT
    #define NETLINK_KOBJECT_UEVENT	15
#endif
#define UDEV_MAX(a,b) ((a) > (b) ? (a) : (b))

static volatile int qudev_exit, qudev_inhib, qudev_hup;

static void asmlinkage sig_handler(int signum);
static int init_udev_monitor_socket(void);
static int init_uevent_netlink_sock(void);
static const char *search_key(const char *searchkey, const char *buf, size_t buflen);
int read_conf(char ***_devpath, char ***_action, char ***_subsys, char ***_command, char const *pathname);

int main(int argc, char **argv)
{
	int status;
	pid_t pid;
	if(argc > 2){
		printf("Usage: qudevmon [-v verbose]\n       qudevmon [-d daemon]\n");
		return(0);
	}
	if(argc == 2){
		if(strcmp(*(argv +1), "-v") && strcmp(*(argv +1), "-d")){
			printf("Usage: qudevmon [-v verbose]\n       qudevmon [-d daemon]\n");
			return(0);
		}
		if(!strcmp(*(argv +1), "-d")){
			pid = fork();
			if(pid == -1){
				fprintf(stderr, "the process can't run as a daemon: %s\n", strerror(errno));
				return(0);
			}
			else if(pid){
				waitpid(pid, &status, WNOHANG);
				return(0);
			}
		}
	}
	if(getuid() != 0){
		fprintf(stderr, "you needed root privileges for running this program\n");
		return(0);
	}
	char buf[UEVENT_BUFFER_SIZE*2];
	char **_devpath = NULL, **_action = NULL, **_subsys = NULL, **_command = NULL;
	const char *devpath, *action, *subsys;
	int i, n_task, fdcount, uevent_netlink_sock, udev_monitor_sock;
	struct sigaction act;
	fd_set readfds;
	ssize_t buflen;
	ssize_t keys;
	
	/* set signal handlers */
	memset(&act, 0x00, sizeof(struct sigaction));
	act.sa_handler = (void (*)(int)) sig_handler;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_RESTART;
	sigaction(SIGINT, &act, NULL);
	sigaction(SIGTERM, &act, NULL);
	sigaction(SIGUSR1, &act,NULL);
	sigaction(SIGUSR2, &act,NULL);
	sigaction(SIGHUP, &act, NULL);
	
	udev_monitor_sock = init_udev_monitor_socket();
	if(udev_monitor_sock == -1)
		return(0);
	uevent_netlink_sock = init_uevent_netlink_sock();
	if(uevent_netlink_sock == -1){
		if(udev_monitor_sock >= 0)
			close(udev_monitor_sock);
		return(0);
	}
	//************************************************
	n_task = read_conf(&_devpath, &_action, &_subsys, &_command, "/etc/qudevmon.conf");
	if(n_task == -1) return(0);
	while(!qudev_exit){
		buflen = 0;
		FD_ZERO(&readfds);
		if(qudev_hup){
			n_task = read_conf(&_devpath, &_action, &_subsys, &_command, "/etc/qudevmon.conf");
			qudev_hup = 0;
		}
		if (uevent_netlink_sock >= 0)
			FD_SET(uevent_netlink_sock, &readfds);
		if (udev_monitor_sock >= 0)
			FD_SET(udev_monitor_sock, &readfds);
		fdcount = select(UDEV_MAX(uevent_netlink_sock, udev_monitor_sock)+1, &readfds, NULL, NULL, NULL);
		if (fdcount < 0) {
			if (errno != EINTR)
				fprintf(stderr, "error receiving uevent message: %s\n", strerror(errno));
			continue;
		}
		if ((uevent_netlink_sock >= 0) && FD_ISSET(uevent_netlink_sock, &readfds)){
			buflen = recv(uevent_netlink_sock, &buf, sizeof(buf), 0);
			if (buflen <= 0) {
				fprintf(stderr, "error receiving uevent message: %s\n", strerror(errno));
				continue;
			}
		}
		if ((udev_monitor_sock >= 0) && FD_ISSET(udev_monitor_sock, &readfds)) {
			buflen = recv(udev_monitor_sock, &buf, sizeof(buf), 0);
			if (buflen <= 0) {
				fprintf(stderr, "error receiving udev message: %s\n", strerror(errno));
				continue;
			}
		}
		if (buflen == 0)
			continue;
		keys = strlen(buf) + 1;
		devpath = search_key("DEVPATH", &buf[keys], buflen);
		action = search_key("ACTION", &buf[keys], buflen);
		subsys = search_key("SUBSYSTEM", &buf[keys], buflen);
		if(argc == 2)
			if(!strcmp(*(argv +1), "-v")){
				printf("DEVPATH - %s\nACTION - %s\nSUBSYSTEM - %s\n", devpath, action, subsys);
				printf("----------------------------------------\n");
				continue;
			}
		for(i = 0; i < n_task && !qudev_inhib; i++)
			if(!strcmp(devpath, *(_devpath + i)) && !strcmp(action, *(_action + i)) && !strcmp(subsys, *(_subsys + i))){
				pid = fork();
				if(pid == 0){
					int pseudo_sock;
					pseudo_sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
					dup2(pseudo_sock, udev_monitor_sock);
					dup2(pseudo_sock, uevent_netlink_sock);
					close(pseudo_sock);
					execl(INTERPRETERPATH, INTERPRETERPATH, "-c", *(_command + i));
					exit(0);
				}
				waitpid(pid, &status, WNOHANG);
			}
	}
	if (uevent_netlink_sock >= 0)
		close(uevent_netlink_sock);
	if (udev_monitor_sock >= 0)
		close(udev_monitor_sock);
	for(i = 0; i < n_task; i++){
		free(*(_devpath + i));
		free(*(_action + i));
		free(*(_subsys + i));
		free(*(_command + i));
	}
	free(_devpath);
	free(_action);
	free(_subsys);
	free(_command);
	return (0);
}

static void asmlinkage sig_handler(int signum)
{
	switch(signum)
	{
		case SIGINT:
			qudev_exit = 1;
			return;
		case SIGTERM:
			qudev_exit = 1;
			return;
		case SIGHUP:
			qudev_hup = 1;
			return;
		case SIGUSR1:
			qudev_inhib = 1;
			return;
		case SIGUSR2:
			qudev_inhib = 0;
		default:
			return;
	}
}

static int init_udev_monitor_socket(void)
{
	int retval;
	const int feature_on = 1;
	static int udev_monitor_sock = -1;
	struct sockaddr_un saddr;
	socklen_t addrlen;

	memset(&saddr, 0x00, sizeof(saddr));
	saddr.sun_family = AF_LOCAL;
	/* use abstract namespace for socket path */
	strcpy(&saddr.sun_path[1], "/org/kernel/qudevmon");
	addrlen = offsetof(struct sockaddr_un, sun_path) + strlen(saddr.sun_path+1) + 1;
	udev_monitor_sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
	if (udev_monitor_sock == -1) {
		fprintf(stderr, "error getting socket: %s\n", strerror(errno));
		return -1;
	}
	/* the bind takes care of ensuring only one copy running */
	retval = bind(udev_monitor_sock, (struct sockaddr *) &saddr, addrlen);
	if (retval < 0) {
		fprintf(stderr, "bind failed: %s\n", strerror(errno));
		close(udev_monitor_sock);
		return -1;
	}
	/* enable receiving of the sender credentials */
	setsockopt(udev_monitor_sock, SOL_SOCKET, SO_PASSCRED, &feature_on, sizeof(feature_on));
	return(udev_monitor_sock);
}

static int init_uevent_netlink_sock(void)
{
	int retval, uevent_netlink_sock = -1;
	struct sockaddr_nl snl;
		
	memset(&snl, 0x00, sizeof(struct sockaddr_nl));
	snl.nl_family = AF_NETLINK;
	snl.nl_pid = getpid();
	snl.nl_groups = 1;
	uevent_netlink_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
	if (uevent_netlink_sock == -1) {
		fprintf(stderr, "error getting socket: %s\n", strerror(errno));
		return -1;
	}
	retval = bind(uevent_netlink_sock, (struct sockaddr *) &snl,
				  sizeof(struct sockaddr_nl));
	if (retval < 0) {
		fprintf(stderr, "bind failed: %s\n", strerror(errno));
		close(uevent_netlink_sock);
		return -1;
	}
	return(uevent_netlink_sock);
}

static const char *search_key(const char *searchkey, const char *buf, size_t buflen)
{
	size_t bufpos = 0;
	size_t searchkeylen = strlen(searchkey);

	while (bufpos < buflen) {
		const char *key;
		int keylen;
		key = &buf[bufpos];
		keylen = strlen(key);
		if (keylen == 0)
			break;
		 if ((strncmp(searchkey, key, searchkeylen) == 0) && key[searchkeylen] == '=')
			return &key[searchkeylen + 1];
		bufpos += keylen + 1;
	}
	return NULL;
}

int read_conf(char ***_devpath, char ***_action, char ***_subsys, char ***_command, char const *pathname)
{
	char *buf, *buf_t, *t_buf, *buf2, *buf3, *buf4;
	int count = 0, n = 255;
	FILE *fs;
	
	fs = fopen(pathname, "r");
	if(!fs){
		fprintf(stderr, "error reading configuration file: %s\n", strerror(errno));
		return -1;
	}
	if(*_devpath){
		free(*_devpath);
		*_devpath = NULL;
	}
	if(*_action){
		free(*_action);
		*_action = NULL;
	}
	if(*_subsys){
		free(*_subsys);
		*_subsys = NULL;
	}
	if(*_command){
		free(*_command);
		*_command = NULL;
	}
	buf = calloc(n + 1, sizeof(char));
	while(!feof(fs)){
		getline(&buf, &n, fs);
		t_buf = buf;
		while(*t_buf == ' ' || *t_buf == '\t') t_buf++;
		if(*t_buf == '#') continue;
		if(*t_buf == '\n') continue;
		if(*t_buf == '\0') continue;
		buf_t = strchr(t_buf, '\n');
		if(buf_t) *buf_t = '\0';
		buf_t = strchr(t_buf, '\t');
		buf2 = strchr(t_buf, ' ');
		if(!buf_t && !buf2) continue;
		if(!buf2 || buf2 > buf_t) buf2 = buf_t;
		while(*buf2 == '\t' || *buf2 == ' ') *buf2++ = '\0';
		buf_t = strchr(buf2, '\t');
		buf3 = strchr(buf2, ' ');
		if(!buf_t && !buf3) continue;
		if(!buf3 || buf3 > buf_t) buf3 = buf_t;
		while(*buf3 == '\t' || *buf3 == ' ') *buf3++ = '\0';
		buf_t = strchr(buf3, '\t');
		buf4 = strchr(buf3, ' ');
		if(!buf_t && !buf4) continue;
		if(!buf4 || buf4 > buf_t) buf4 = buf_t;
		while(*buf4 == '\t' || *buf4 == ' ') *buf4++ = '\0';
		*_devpath = realloc(*_devpath, (count + 1)*sizeof(char **));
		*(*_devpath + count) = calloc(strlen(t_buf) + 1, sizeof(char));
		strcpy(*(*_devpath + count), t_buf);
		*_action = realloc(*_action, (count + 1)*sizeof(char **));
		*(*_action + count) = calloc(strlen(buf2) + 1, sizeof(char));
		strcpy(*(*_action + count), buf2);
		*_subsys = realloc(*_subsys, (count + 1)*sizeof(char **));
		*(*_subsys + count) = calloc(strlen(buf3) + 1, sizeof(char));
		strcpy(*(*_subsys + count), buf3);
		*_command = realloc(*_command, (count + 1)*sizeof(char **));
		*(*_command + count) = calloc(strlen(buf4) + 1, sizeof(char));
		strcpy(*(*_command + count), buf4);
		*buf = '\0';
		count++;
	}
	free(buf);
	fclose(fs);
	return(count);
}
