/*
 * Copyright (C) 2001-2002 by CERN/IT/PDP/DM
 * All rights reserved
 */

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Cinit.h"
#include "getconfent.h"
#include "marshall.h"
#include "net.h"
#include "rbtsubr_constants.h"
#include "rmc_constants.h"
#include "rmc_logit.h"
#include "rmc_procreq.h"
#include "rmc_sendrep.h"
#include "rmc_smcsubr.h"
#include "scsictl.h"
#include "serrno.h"
#include "Cdomainname.h"
#include <sys/types.h>
#include <sys/stat.h>
#include "rmc_send_scsi_cmd.h"

#define PATH_CONF "/etc/cta/cta-rmcd.conf"

/* Forward declaration */
static int rmc_getreq(const int s, int *const req_type, char *const req_data,
  char **const clienthost);
static void rmc_procreq(const int rpfd, const int req_type, char *const req_data,
  char *const clienthost);
static int rmc_dispatchRqstHandler(const int req_type,
  const struct rmc_srv_rqst_context *const rqst_context);
static void rmc_doit(const int rpfd);

int jid;
char localhost[CA_MAXHOSTNAMELEN+1];
int maxfds;
struct extended_robot_info extended_robot_info;

int rmc_main(const char *const robot)
{
	int c;
	char domainname[CA_MAXHOSTNAMELEN+1];
	struct sockaddr_in from;
	socklen_t fromlen = sizeof(from);
	const char *msgaddr;
	int on = 1;	/* for REUSEADDR */
	fd_set readfd, readmask;
	int s;
	struct sockaddr_in sin;
	struct smc_status smc_status;
	struct timeval timeval;
	char func[16];

	strncpy (func, "rmc_serv", sizeof(func));
	func[sizeof(func) - 1] = '\0';

	jid = getpid();
	rmc_logit (func, "started\n");
        
	gethostname (localhost, CA_MAXHOSTNAMELEN+1);
	if (strchr (localhost, '.') == NULL) {
		if (Cdomainname (domainname, sizeof(domainname)) < 0) {
			rmc_logit (func, "Unable to get domainname\n");
		}
		strcat (localhost, ".");
		strcat (localhost, domainname);
	}

	if (*robot == '\0' ||
	    (strlen (robot) + (*robot == '/') ? 0 : 5) > CA_MAXRBTNAMELEN) {
		rmc_logit (func, RMC06, "robot");
		exit (USERR);
	}
	if (*robot == '/')
		strcpy (extended_robot_info.smc_ldr, robot);
	else
		sprintf (extended_robot_info.smc_ldr, "/dev/%s", robot);

	extended_robot_info.smc_fd = -1;

	/* get robot geometry */
	{
		const int max_nb_attempts = 3;
		int attempt_nb = 1;
		for(attempt_nb = 1; attempt_nb <= max_nb_attempts;
                        attempt_nb++) {
                        rmc_logit (func,
                                "Trying to get geometry of tape library"
                                ": attempt_nb=%d\n", attempt_nb);
			c = smc_get_geometry (extended_robot_info.smc_fd,
				extended_robot_info.smc_ldr,
				&extended_robot_info.robot_info);

			if(0 == c) {
                                rmc_logit (func,
                                         "Got geometry of tape library\n");
				break;
			}

			c = smc_lasterror (&smc_status, &msgaddr);
			rmc_logit (func, RMC02, "get_geometry", msgaddr);

                        // If this was the last attempt
			if(max_nb_attempts == attempt_nb) {
				exit(c);
			} else {
                                sleep(1);
                        }
		}
	}

	FD_ZERO (&readmask);
	FD_ZERO (&readfd);
	signal (SIGPIPE, SIG_IGN);
	signal (SIGXFSZ, SIG_IGN);

	/* open request socket */

	if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
		rmc_logit (func, RMC02, "socket", neterror());
		exit (CONFERR);
	}
	memset ((char *)&sin, 0, sizeof(struct sockaddr_in)) ;
	sin.sin_family = AF_INET ;
	{
		const char *p;
		if ((p = getenv ("RMC_PORT")) || (p = getconfent_fromfile (PATH_CONF,"RMC", "PORT", 0))) {
			sin.sin_port = htons ((unsigned short)atoi (p));
		} else {
			sin.sin_port = htons ((unsigned short)RMC_PORT);
		}
	}
	// rmcd should only accept connections from the loopback interface
	sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) {
		rmc_logit (func, RMC02, "setsockopt", neterror());
        }
	if (bind (s, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		rmc_logit (func, RMC02, "bind", neterror());
		exit (CONFERR);
	}
	listen (s, 5) ;

	FD_SET (s, &readmask);

		/* main loop */

	while (1) {
		if (FD_ISSET (s, &readfd)) {
			FD_CLR (s, &readfd);
			const int rpfd =
				accept (s, (struct sockaddr *) &from, &fromlen);
			(void) rmc_doit (rpfd);
		}
		memcpy (&readfd, &readmask, sizeof(readmask));
		timeval.tv_sec = RMC_CHECKI;
		timeval.tv_usec = 0;
		if (select (maxfds, &readfd, (fd_set *)0, (fd_set *)0, &timeval) < 0) {
			FD_ZERO (&readfd);
		}
	}
}

/**
 * Returns 1 if the rmcd daemon should run in the background or 0 if the
 * daemon should run in the foreground.
 */
static int run_rmcd_in_background(const int argc, char **argv) {
  int i = 0;
  for(i = 1; i < argc; i++) {
    if(0 == strcmp(argv[i], "-f")) {
      return 0;
    }
  }

  return 1;
}

/**
 * Returns 1 if the specified command-line argument start with '-' else
 * returns 0.
 */
static int cmdline_arg_is_an_option(const char *arg) {
  if(strlen(arg) < 1) {
    return 0;
  } else {
    if('-' == arg[0]) {
      return 1;
    } else {
      return 0;
    }
  }
}

/**
 * Returns the number of command-line arguments that start with '-'.
 */
static int get_nb_cmdline_options(const int argc, char **argv) {
  int i = 0;
  int nbOptions = 0;

  for(i = 1; i < argc; i++) {
    if(cmdline_arg_is_an_option(argv[i])) {
      nbOptions++;
    }
  }

  return nbOptions;
}

int main(const int argc,
         char **argv)
{
	const char *robot = "";
	const int nb_cmdline_options = get_nb_cmdline_options(argc, argv);

	switch(argc) {
	case 1:
		fprintf(stderr, "RMC01 - wrong arguments given ,specify the device file of the tape library\n");
		exit (USERR);
	case 2:
		if(0 == nb_cmdline_options) {
			robot = argv[1];
		} else {
			fprintf(stderr, "RMC01 - robot parameter is mandatory\n");
			exit (USERR);
		}
		break;
	case 3:
		if(0 == nb_cmdline_options) {
			fprintf(stderr, "Too many robot parameters\n");
			exit (USERR);
		} else if(2 == nb_cmdline_options) {
			fprintf(stderr, "RMC01 - robot parameter is mandatory\n");
			exit (USERR);
		/* At this point there is one argument starting with '-' */
		} else if(0 == strcmp(argv[1], "-f")) {
			robot = argv[2];
		} else if(0 == strcmp(argv[2], "-f")) {
			robot = argv[1];
		} else {
			fprintf(stderr, "Unknown option\n");
			exit (USERR);
		}
		break;
	default:
		fprintf(stderr, "Too many command-line arguments\n");
		exit (USERR);
	}

	if(run_rmcd_in_background(argc, argv)) {
		if ((maxfds = Cinitdaemon ("rmcd", NULL)) < 0) {
			exit (SYERR);
		}
	}
	exit (rmc_main (robot));
}

static void rmc_doit(const int rpfd)
{
	int c;
	char *clienthost;
	char req_data[RMC_REQBUFSZ-3*LONGSIZE];
	int req_type = 0;

	if ((c = rmc_getreq (rpfd, &req_type, req_data, &clienthost)) == 0)
		rmc_procreq (rpfd, req_type, req_data, clienthost);
	else if (c > 0)
		rmc_sendrep (rpfd, RMC_RC, c);
	else
		close (rpfd);
}

static int rmc_getreq(
  const int s,
  int *const req_type,
  char *const req_data,
  char **const clienthost)
{
	struct sockaddr_in from;
	socklen_t fromlen = sizeof(from);
	struct hostent *hp;
	int l;
	int magic;
	int msglen;
	int n;
	char *rbp;
	char req_hdr[3*LONGSIZE];
	char func[16];

	strncpy (func, "rmc_getreq", sizeof(func));
	func[sizeof(func) - 1] = '\0';

	l = netread_timeout (s, req_hdr, sizeof(req_hdr), RMC_TIMEOUT);
	if (l == sizeof(req_hdr)) {
		rbp = req_hdr;
		unmarshall_LONG (rbp, magic);
		unmarshall_LONG (rbp, n);
		*req_type = n;
		unmarshall_LONG (rbp, msglen);
		if (msglen > RMC_REQBUFSZ) {
			rmc_logit (func, RMC46, RMC_REQBUFSZ);
			return (-1);
		}
		l = msglen - sizeof(req_hdr);
		n = netread_timeout (s, req_data, l, RMC_TIMEOUT);
		if (getpeername (s, (struct sockaddr *) &from, &fromlen) < 0) {
			rmc_logit (func, RMC02, "getpeername", neterror());
			return (ERMCUNREC);
		}
		hp = gethostbyaddr ((char *)(&from.sin_addr),
			sizeof(struct in_addr), from.sin_family);
		if (hp == NULL)
			*clienthost = inet_ntoa (from.sin_addr);
		else
			*clienthost = hp->h_name ;
		return (0);
	} else {
		if (l > 0) {
			rmc_logit (func, RMC04, l);
		} else if (l < 0) {
			rmc_logit (func, RMC02, "netread", sstrerror(serrno));
                }
		return (ERMCUNREC);
	}
}

static void rmc_procreq(
  const int rpfd,
  const int req_type,
  char *const req_data,
  char *const clienthost)
{
	struct rmc_srv_rqst_context rqst_context;

	rqst_context.localhost = localhost;
	rqst_context.rpfd = rpfd;
	rqst_context.req_data = req_data;
	rqst_context.clienthost = clienthost;

	const int handlerRc = rmc_dispatchRqstHandler(req_type, &rqst_context);

	if(ERMCUNREC == handlerRc) {
		rmc_sendrep (rpfd, MSG_ERR, RMC03, req_type);
	}
	rmc_sendrep (rpfd, RMC_RC, handlerRc);
}

/**
 * Dispatches the appropriate request handler.
 *
 * @param req_type The type of the request to be handled.
 * @param rqst_context The context of the request.
 * @return The result of handling the request.
 */
static int rmc_dispatchRqstHandler(const int req_type,
	const struct rmc_srv_rqst_context *const rqst_context) {
	switch (req_type) {
	case RMC_MOUNT:
		return rmc_srv_mount (rqst_context);
	case RMC_UNMOUNT:
		return rmc_srv_unmount (rqst_context);
	case RMC_EXPORT:
		return rmc_srv_export (rqst_context);
	case RMC_IMPORT:
		return rmc_srv_import (rqst_context);
	case RMC_GETGEOM:
		return rmc_srv_getgeom (rqst_context);
	case RMC_READELEM:
		return rmc_srv_readelem (rqst_context);
	case RMC_FINDCART:
		return rmc_srv_findcart (rqst_context);
	default:
		return ERMCUNREC;
	}
}