/*
 * Copyright (C) 2007-2008, Intel Corp. All rights reserved.
 *
 *
 * This program is free software; you can 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */


/*
 * Intel SGPIO enclosure management utility
 * Author: Eric R Hall <Eric.R.Hall@intel.com>
 * Rework: Krzysztof Wojcik <krzysztof.wojcik@intel.com>
 *
 */

#include <stdio.h>     /* used by printf */
#include <unistd.h>    /* used by chdir and write */
#include <stdlib.h>    /* used by exit */
#include <math.h>      /* used by math functions */
#include <getopt.h>    /* used by command.c to parse the command line */
#include <string.h>    /* used to manipulate strings */
#include <fcntl.h>     /* used for O_WRONLY definition in 'open()' */
#include <time.h>      /* used by usleep because SGPIO driver is slow */
#include <sys/time.h>  /* needed by setitimer */
#include <signal.h>    /* needed by signals */
#include <syslog.h>    /* used for message log */
#include <sys/types.h> /* used by read() */
#include <dirent.h>

/***************** 80 chars ***************************************************/

/* With Kristen's new 2.6.24-rc3 patch the LED locations are:
 * /sys/class/scsi_host/hostX/em_message
 * the status of the LED can be catted from the location above.
 * Write to it using decimal and read from it using hex
 */

/* this programs version and sgpio spec version */
#include "version.h"

/* this programs available commands */
#include "commands.h"

/* define the directory paths to the LED "files" */
#define MAX_DISK_IDX    5  
#define SYSFS_BASE      "/sys/class"
#define PORT_NAME	"/scsi_host/host" /* host0 = port0 ~ sda */
#define SW_CONTROL	"/scsi_device"
#define EM_MESSAGE	"/em_message"
#define EM_MESSAGE_TYPE	"/em_message_type"
#define EM_MESSAGE_DFLT	"0"

/* per section 12.2.1 in the ACHI 1.2 spec */
#define LOCATE_LED      524288  /* or bitmask 2^19 */
#define FAULT_LED    	4194304 /* or bitmask 2^22 */
#define REBUILD_LED  	4718592 /* 2^19 + 2^22 */
#define ACTIV_LED	65536	/* 2^16 */
#define OFF_LED      	0       /* nothing! */

/* LEDs in the backplane per disk and the order of the leds
 * Although there are only physically two LEDs on an Intel Intelliegent
 * backplane, since each state acts like a LED we need to have all
 * the possible LED states: locate on, fault on, rebuild (locate+fault),
 * and off. */
#define LEDS_PER_DISK  5 /* locate, fault, rebuild, activity, and off */
/* time to wait in uSec before sending next command to the SGPIO bus */
#define SGPIO_WAIT      1500

/* set the buf size for led_write */
#define WRITE_BUF 20

/* some number that's unlikely to be random */
#define INITIATED 1009

/* /sys/class/scsi_device/0:0:0:0/device/block:sda
* the path in the fs is 47 char */
#define FS_PATH_LEN 47

/* message lenth */
#define MESSAGE_LEN 50

/*index of port number in fs path*/
#define PORT_NUM_LOC 21

/*denotes free record in the disk structure*/
#define PORT_FREE -1

#define NONE -1

static const char *sys_scsi_path = "/sys/class/scsi_disk/";
static const char *sys_scsi_dev_blk_sles = "/device/block/";
static const char *sys_scsi_dev_blk_rh = "/device/block:";

/* define global variables */
/* the led list is used to dynamically set the 'LEDs' for each disk */
int _led_list[] = {LOCATE_LED, FAULT_LED, REBUILD_LED, ACTIV_LED, OFF_LED};
/* blink rates in Hz for locate, fault, rebuild, activity */
int _led_rate = 0;
/* SIGALRM flag*/
volatile sig_atomic_t sigal_flag = 0;
/* done defining global variables */

/* led structure; path to control, init'd status, states, and file handler */
struct led_context{
	char path[256];
	int init;
	int current_state;
	int next_state;
	int fd;
};

/* uses the led_context to associate disks to leds */
struct disk{
	struct led_context data[LEDS_PER_DISK];
	int id;
	int host_port;
	int init;
	char name[7];
};

/* structure for the disks associated with the led structure */
struct disk disks[(MAX_DISK_IDX+1)];

/* define the version to return.  Uses version.h */
static int version(){
	printf("Utility Version %i.%i\n", UTIL_VERSION_MAJOR, \
	UTIL_VERSION_MINOR);
	printf("SGPIO Specification Version %i.%i\n", SGPIO_VERSION_MAJOR, \
	SGPIO_VERSION_MINOR);
	return 0;
}

/* display help information */
static int help(char *argv){
	printf("SGPIO tool for intelligent hard disk drive backplanes\nUsage:\n" \
	"%s [[-d|--disk] <sda,sdb,sdc,etc...> | [-p|--port] <0,1,2,etc...>]\n" \
	"[-s|--status] <fault|locate|rebuild|off> [-f|--freq] <1,2,3,etc...>\n",
	argv);
	return 0;
}

/* define the led states */
static void init_led(struct led_context *led){
	led->current_state = 0;
	led->next_state = 0;
}

/* declare the whatportis [sdX] prototype */
int whatportis(const char *disk);

/* declare the getport [number] function prototype */
int getport(struct led_context *data, int port);

/* declare the led_write function prototype */
int led_write(struct led_context *data, int chg_state);

int led_set(int port_num);

/* declare the led_change_state function prototype */
int led_change_state(void);

void led_init(void);

int led_execute(int next_state, char *message);

/* declare the timer event function prototype */
void alarm_handler(int signo);

/* declare the timer function that runs every x sec or usec prototype */
int led_timer();

int find_port_number(char *location);

/* the main */
int main(int argc, char **argv){
	char delims[] = ","; /* used to split --disk argument sda,sdb,sdc */
	char* result;  /* the result of the 'split' disks used in the -d argument */
	int c;  /* used as the command argument case selector */
	int port_num = -1; /* 'case: p' port number */
	int freq = 0;     /* blink rates reset to zero- disable blinking*/
	int next_state = NONE; /* store next sate of LEDs*/
	char message[MESSAGE_LEN]; /* store messages */
	int i = 0;
		
	/* Let's dynamically figure out what this program was called as
	 * in case someone renames the utility. */
	char *progname = argv[0];
	/* if there is a '/' before the program name, then truncate the data
	* before it.  In case there is no '/' before the filename, the
	* if statement resets progname to argv[0].
	* This program will segFault without the test. */
	if((progname = strrchr (progname, '/')) != 0){
	/* remove the first char, '/', from the beginning of the
	 * string by incrementing the pointer location. */
		progname++;
	}
	else{
	/* This will seem cheap, but if we get here then progname
	 * is a null pointer and we need to fill it with something.
	 * In this case, we'll fill it with the called program name. */
		progname = argv[0];
	}
	/* done figuring out the program name. */

	if(argc < 2){
		help(progname);
		exit(1);
	}

	led_init();

	while(1){
		/* getopt_long uses this option index */
		int option_index = 0;
		char *end_str = 0;

		c = getopt_long(argc, argv, short_options, long_options,
		  &option_index);

		/* Detect the end of the options. */
		if(c == -1){
			break;
		}

		switch(c){
			case 0:
				if(long_options[option_index].flag != 0)
					break;
				printf ("option %s",
				        long_options[option_index].name);
				if(optarg)
					printf (" with arg %s", optarg);
				printf ("\n");
				break;
			case 'h':
				/* print help to the screen with the
				 * parsed program name */
				help(progname);
				break;
			case 'p':
				/* split the string at the 'delims' and then loop */
				result = strtok(optarg, delims);
				while(result != NULL){
					port_num = strtol(result, &end_str, 10);
					if(port_num < 0 || *end_str != '\0'){
						printf("Error: %s is not a "
							"valid port number!\n",
							result);
						return -1;
					}
					/* call the led_set function that
					 * initalized and sets the
					 * paths and data for the disks LED */
					if(led_set(port_num) != 0){
						printf("Error: can not initialize "
							"data for disks LED!\n");
						return -1;
					}
					/* move to the next entry after the
					 * current deliminator,
					 * this should be the next port number */
					result = strtok(NULL, delims);
				}
				break;
			case 'd':
				/* split the string at the 'delims' and then loop */
				result = strtok(optarg, delims);
				while(result != NULL){
					if((port_num = whatportis(result)) == -1){
						printf("Error: can not extract "
							"port number!\n");
						return -1;
					}
					/* bounds checking, if anything is
					 * out of bounds here
					 * the tool will segfault - we must quit! */
					if(strlen(result) != 3 || port_num < 0){
						printf("Error: not a valid "
							"device: %s\n", result);
						return -1;

					}
					/* call the led_set function that
					 * initalized and sets the
					 * paths and data for the disks LED */
					if(led_set(port_num) != 0){
						printf("Error: can not initialize "
							"data for disks LED!\n");
						return -1;
					}
					/* move to the next entry after the
					 * current deliminator,
					 *  this should be the next port number */
					result = strtok(NULL, delims);
				}
				break;
			case 's':
				if(!(strcmp(optarg, "locate"))){
					next_state = LOCATE_LED;
					sprintf(message,"Locate LED was ");
				}
				else if(!(strcmp(optarg, "fault"))){
					next_state = FAULT_LED;
					sprintf(message,"Fault LED was ");
				}
				else if(!(strcmp(optarg, "rebuild"))){
					next_state = REBUILD_LED;
					sprintf(message, "Rebuild LED was ");
				}
				else if(!(strcmp(optarg, "off"))){
					next_state = OFF_LED;
					sprintf(message, "Off LED was ");
				}
				else if(!(strcmp(optarg, "activity"))){
					next_state = ACTIV_LED;
					sprintf(message, "Activate LED was ");
				}
				else{
					printf("Error: Unknown option: %s!\n",
					       optarg);
					return -1;
				}
				break;
			case 'f':
				freq = strtol(optarg, &end_str, 10);
				if(freq < 1 || freq > 10 || *end_str != '\0'){
					printf("Error: Invalid frequency, "
						"please use number between 1 and 10!\n");
					return -1;
				}
				else
					_led_rate = freq;
				break;
			case 'V':
				version();
				break;
			default:
				printf("Unknown command\n");
				help(progname);
				exit(1);
			}
		}

	/* Print any remaining command line arguments that were not parsed */
	if(optind < argc){
		printf ("Error!  Unknown arguments: ");
		while (optind < argc)
		printf ("%s ", argv[optind++]);
		printf("\nPlease use -h for more information.\n");
	}

	/* not sure if we need to close the file
	*close(disks[jj].data[0].path); */

	/* start the timer for non inteligent backplanes.
	* It loops continuously because the timer needs
	* to be reset.  */
	if(_led_rate != 0){
		sigal_flag = 1;
		while(1){
		/* use signals to alert the user if the alarm failed */
			if (signal(SIGALRM, alarm_handler) == SIG_ERR) {
				printf("Error: Unable to catch SIGALRM\n!");
			}
			if(sigal_flag){
				if(led_timer() != 0){
					printf("Error: Unable to set "
						"LEDs timer!\n");
					exit(0);
				}
				if(led_change_state() != 0){
					printf("Error: Unable to change "
						"state of LEDs\n");
					exit(0);
				}
				sigal_flag = 0;
			}
		}
	}
	if(next_state == NONE){
		printf("Error: State of LED must be specified!\n");
		exit(0);
	}
	while(disks[i++].host_port == PORT_FREE){
		if(i > MAX_DISK_IDX){
			printf("Error: Port number or disk name must be specified!\n");
			exit(0);
		}
	}
	if(led_execute(next_state, message) != 0){
		printf("Error: Unable to execute set command!\n");
	}
	exit(0);
}

void led_init(void){
	int i;
	for(i=0;i<=MAX_DISK_IDX;i++){
		disks[i].host_port = PORT_FREE;
	}
}

/* since we don't know which disk LEDs have been
 * init'd we'll just cycle through all of them.
 */
int led_execute(int next_state, char *message){
	int leds = 0;
	int ii = 0;
	for(ii=0;ii<=MAX_DISK_IDX; ii++){
		/* the problem with cycling through all of the disks
		 * is that the un-init'd ones have junk data that we
		 * don't want to write to the fs; a simple check is
		 * made to make sure that the disk was init'd. */
		for(leds=0;leds<LEDS_PER_DISK;leds++){
			if((disks[ii].data[leds].next_state == next_state)
					&& (disks[ii].init == INITIATED)){
				disks[ii].data[leds].init = INITIATED;
				if(led_write(&disks[ii].data[leds], 1) == -1){
					syslog(LOG_INFO, "%snot set for %s\n",
					       message, disks[ii].name);
					if(verbose_flag)
						printf("%snot set for %s\n",
						       message, disks[ii].name);
					return -1;
				}
				else{
					syslog(LOG_INFO, "%sset for %s\n",
					       message, disks[ii].name);
					if(verbose_flag)
						printf("%sset for %s\n",
						       message, disks[ii].name);
				}
			}
		}
	}
	return 0;
}

int led_change_state(void){
	int myleds = 0;
	int mydisks = 0;

	for(mydisks=0;mydisks<=MAX_DISK_IDX;mydisks++){
		for(myleds=0;myleds<LEDS_PER_DISK;myleds++){
			if(disks[mydisks].data[myleds].init == INITIATED){
				if(verbose_flag){
					printf("Timer:\n\tname: %s\n",
					       disks[mydisks].name);
					printf("\tdisk: %d led: %d\n",
					       mydisks, myleds);
					printf("\tcurrent_state: %d\n",
					       disks[mydisks].data[myleds].current_state);
					printf("\tnext_state: %d\n",
					       disks[mydisks].data[myleds].next_state);
					printf("\tinit: %d\n\n",
					       disks[mydisks].data[myleds].init);
				}
				if(led_write(&disks[mydisks].data[myleds], 1) == -1){
					printf("Error: Failed to set led %d for "
					       "port %d!\n", myleds, disks[mydisks].host_port);//fix!!!!
					return -1;
				}
			}
		}
	}
	return 0;
}

int led_write(struct led_context *data, int chg_state){

	/* set the return state in case write fails to */
	int return_state = -1;

	/* this timeout is needed because the utility
	 * will write to the sgpio bus too fast and
	 * the Intel backplane will miss the data */
	usleep(SGPIO_WAIT);

	/* get the next state of the led from the data structure */
	int state = data->next_state;

	/* if the change state is set to 0, then the LED should be turned off
	* and the state from the data structure should be ignored */
	if(chg_state == 0)
		state = 0;

	/* create a char buffer to store the led state since
	* we will be printing it as a char/string to the fs */
	char buf[WRITE_BUF];

	/* cast buf with a string of the decimal value of the led state */
 	if(sprintf(buf, "%d", state) < 1){
		printf("Error: Unable to write LED state to buffor!\n");
		return -1;
	}

 	/* This is just debug to make sure the 'data' is correct */
 	if(verbose_flag){
 	printf("led_write: led state: %d led next state: %d path: %s\n",
 	       data->current_state, state, data->path);
 	}

 	/* use the file handler in the data struct to print the buffer
 	 * and only print x bytes of the size of the buffer.
 	 * The number returned from write is the number of bytes written.
 	 * Write should only ever write two bytes, else it's a failure
 	 */
 	return_state = write(data->fd,buf,strlen(buf));
	if(return_state == -1){
		printf("Error: Unable to write to descriptor!\n");
		return return_state;
	}
	data->next_state = data->current_state;
	data->current_state = state;

	/* should return the buf len */
	return return_state;
}

int led_set(int port_num){
	int leds = 0;
	int index = 0;

	while(disks[index].host_port != PORT_FREE){
		if(++index > MAX_DISK_IDX){
			printf("Error: Too many disks!\n");
			return -1;
		} 
	}

	/* Give the port a name */
	if(sprintf(disks[index].name,"Port %d", port_num) < 0){
		printf("Error: Unable to write port number to buffor!\n");
		return -1;
	}
	disks[index].init = INITIATED;
	/* set up the LEDs for each port */
	for(leds=0;leds<LEDS_PER_DISK;leds++){
		/* creates the inital LED struct with
		 * current_state and next_state */
		init_led(&disks[index].data[leds]);

		/* if the disk has already been init'd then skip it.
		 * This would only occur if the user specified the
		 * same port/disk twice
		if (disks[port_num].data[leds].init == INITIATED)
		    break;
		*/

		/* populate the port path */
		if(sprintf(disks[index].data[leds].path,"%s%s%d%s",\
				SYSFS_BASE, PORT_NAME, port_num, EM_MESSAGE) < 0){
			printf("Error: Unable to write port path to buffor!\n");
			return -1;
		}

		disks[index].data[leds].next_state = _led_list[leds];


		if(verbose_flag){
			printf("port %d leds %d\n" \
			       "next_state %d init %d\n", port_num, leds,
			       disks[index].data[leds].next_state,
			       disks[index].data[leds].init);
		}

		/* create the file handler and set it as read/write
		 * to allow port_status to read em_message */
		disks[index].data[leds].fd =
			open(disks[index].data[leds].path, O_RDWR,0);

		/* if the file handler failed, the path to the
		 * requested disk was not found, we'll need to quit */
		if(disks[index].data[leds].fd < 0){
			printf("Error: Unable to find path to requested disk: port %d!\n", 
				port_num);
			return -1;
		}
	}
	/* done setting up the LEDs */
	disks[index].host_port = port_num;
	return 0;
}

int find_port_number(char *location){
	DIR *dir;
	char *ptr = NULL;
	int port = -1;
	if((dir = opendir(location))){
		closedir(dir);
		ptr = strchr((const char *)&location[PORT_NUM_LOC], ':');
		if(ptr){
			*ptr = 0;
			port = atoi((char *) &location[PORT_NUM_LOC]);
		}
	}
	return port;
}
	

int whatportis(const char *disk){
	int port = -1;
	/* /sys/class/scsi_device/0:0:0:0/device/block:sda
	 * the path in the fs is 47 char */
	char location[FS_PATH_LEN+1];

	char dir_qan, len;
	int i;
	struct dirent **dir_ent;

	/* quit if disk is not 3 chars */
	if(strlen(disk) != 3)
		return -1;

	dir_qan = scandir(sys_scsi_path, &dir_ent, 0, alphasort);
	if(dir_qan < 0){
		printf("Error: /sys/class/scsi_disk not contain any directory\n");
		return -1;
	}

	strcpy(location, sys_scsi_path);
	len = strlen(location);

	for(i=0;i<dir_qan;i++){
		const char *dir_name = dir_ent[i]->d_name;
		if(port == -1){
			sprintf(location + len, "%s%s%s", 
				dir_name, sys_scsi_dev_blk_sles, disk);
			port = find_port_number(location);
			if(port == -1){
				sprintf(location + len, "%s%s%s", 
					dir_name, sys_scsi_dev_blk_rh, disk);
				port = find_port_number(location);
			}
		}
		free(dir_ent[i]);
	}
	free(dir_ent);
	return port;
}

void alarm_handler(int signo){
	sigal_flag = 1;
}

/* set up the timer function that runs every x sec or usec */
int led_timer(){
	/* create the delay structure */
	struct itimerval delay;
	int ret;

	float temp_rate = 1.0/_led_rate/2;
	int rate = temp_rate*1000000;

	/* the time values for the alarm.  It has uS granularity */
	delay.it_value.tv_sec = 0;
	delay.it_value.tv_usec = rate;
	delay.it_interval.tv_sec = 0;
	delay.it_interval.tv_usec = rate;

	/* reset the timer with the above time values */
	ret = setitimer(ITIMER_REAL, &delay, NULL);

	/* if the timer failed to reset, error out */
	if(ret){
		perror("setitimer\n");
		return -1;
	}
	return 0;
}


