/* Read devices from a /proc/bus/input/devices-like file
 * Version 1.0 (17/02/2008)
 *
 * Written by Daniel Collins <solemnwarning@solemnwarning.net>
 * Released as public domain
 *
 * If you find any bugs in this code please email me :)
*/

/* Version history:
 *
 * 1.0 (17/02/2008):
 *	Initial release
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "devices.h"

#define DIE(...) \
	snprintf(input_errmsg, 1023, __VA_ARGS__);\
	\
	free_devices(retptr);\
	free(nnode);\
	\
	while(fh && fclose(fh) != 0) {\
		if(errno == EINTR) {\
			continue;\
		}\
		\
		break;\
	}\
	return (input_dev_list*)input_errmsg;

#define INIT_NNODE() \
	nnode = malloc(sizeof(input_dev_list));\
	if(!nnode) {\
		DIE("Can't allocate memory");\
	}\
	DEVICE_DEFAULTS(nnode);

char input_errmsg[1024] = {'\0'};

/* Fetch devices from the specified DEVFILE file
 * This doesn't do much sanity checking, an invalid devices file may cause
 * segfaults!
 *
 * Returns a list of devices (NULL for none) on success, on error a pointer to
 * input_errmsg is returned and the error message will be written there.
*/
input_dev_list *fetch_devices(void) {
	input_dev_list *retptr = NULL;
	input_dev_list *nnode = NULL;
	
	FILE *fh = NULL;
	while(!(fh = fopen(DEVFILE, "r"))) {
		if(errno == EINTR) {
			continue;
		}
		
		DIE("Can't open " DEVFILE ": %s", strerror(errno));
	}
	
	char line[1024] = {'\0'};
	INIT_NNODE();
	
	while(fgets(line, 1024, fh)) {
		if(line[0] == 'I') {
			nnode->bus = strtoul(strstr(line, "Bus=")+4, NULL, 16);
			nnode->vendor = strtoul(strstr(line, "Vendor=")+7, NULL, 16);
			nnode->product = strtoul(strstr(line, "Product=")+8, NULL, 16);
			nnode->version = strtoul(strstr(line, "Version=")+8, NULL, 16);
		}
		if(line[0] == 'N') {
			strncpy(nnode->name, strchr(line, '\"')+1, 1023);
			*(strrchr(nnode->name, '\"')) = '\0';
		}
		if(line[0] == 'P') {
			strncpy(nnode->phys, strchr(line, '=')+1, 1023);
			*(strrchr(nnode->phys, '\n')) = '\0';
		}
		if(line[0] == 'H') {
			char* tmp = strtok(strchr(line, '=')+1, " ");
			
			while(tmp && nnode->hcount < MAX_HANDLERS) {
				if(tmp[0] == '\n') {
					break;
				}
				strncpy(nnode->handlers[(nnode->hcount)++], tmp, 63);
				
				tmp = strtok(NULL, " ");
			}
		}
		
		if(line[0] == '\n') {
			nnode->next = retptr;
			retptr = nnode;
			
			INIT_NNODE();
		}
	}
	if(ferror(fh)) {
		DIE("Can't read " DEVFILE ": %s", strerror(errno));
	}
	
	while(fclose(fh) != 0) {
		if(errno == EINTR) {
			continue;
		}
		
		DIE("Can't close " DEVFILE ": %s", strerror(errno));
	}
	
	return retptr;
}

/* Free an entire list of devices
 * Passing NULL is a safe no-op.
*/
void free_devices(input_dev_list *list) {
	input_dev_list *dptr = NULL;
	
	while(list) {
		dptr = list;
		list = list->next;
		
		free(dptr);
	}
}
