/*==========================================================================*\
 |  This file is part of setiherder (http://www.sackheads.org/setiherder/)  |
 |  which was written by Brian L. Naylor <brian+seti@sackheads.org> and is  |
 |  under the GNU General Public License.  You should have recieved a copy  |
 |  of the GPL with this source code.  If not, we'd like to know about it.  |
\*--------------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

#include "shrc.h"
#include "shutil.h"
#include "shclients.h"

#define SH_RC_NAME ".setiherderrc"

#ifdef AIX
extern char *sys_errlist[];
#endif

/**************************************************************************\
  RC FILE SYNTAX:
  
  Note that users generally will not even need to look at this, much
  less edit it.  This is here for posterity. :)
  
  There are no official provisions for comments or whitespace-only lines, 
  because this file will be re-written during the course of normal 
  execution.  
  
  Configuration key/value pairs take the following form:
  
    option_keyword=value
    
  Host sections begin with the hostname in square [] brackets and continue
  until the next set of brackets. 
  
  Current valid global keywords:
    hostname_column_width  : integer, sets width of host column in clist
    account_column_width   : "           
    state_column_width     : "
    units_column_width     : "
    complete_column_width  : "
    window_width           : integer, sets width of main window
    window_height          : "
    
  Current valid host keywords:
    seticmd                : string, sets command used to execute seti@home *
    autores                : integer, flag determining if client gets restarted
    delay                  : integer, seconds to wait between crash and restart
 
  * indicates required options                            
                             
  An example:
  -----------
  # my very own setiherder example rc file
  
  # I have really long hostnames
  hostname_column_width=300
  
  [penfold]
    seticmd=ssh -t penfold /usr/local/seti/setistart
    autores=1
    delay=300
    
  [pinky]
    seticmd=ssh -t -i /home/bnaylor/.ssh/pinkt_key pinky /tmp/setistart
    autores=0
    
\**************************************************************************/



/*
 * XXX HERE BE DRAGONS XXX
 *
 * This is kinda gross.  I just extern various globals like the client
 * list, some window settings, and save/alter those.  It could do with
 * a real design, and will probably get one..  some day.  For now, this
 * is what we're working with.  It's simple enough I don't think it'll
 * cause any trouble.  (famous last words :)
 */

/*
 * EXTERNAL GLOBALS
 */
extern sh_client_t list[];        /* client list                           */
extern int sh_client_idx;         /* client list index                     */

extern int clist_host_width,      /* widths of the client clist fields     */
           clist_account_width,
           clist_state_width,
           clist_units_width,
           clist_complete_width;

extern int main_window_width,     /* main window size                      */
           main_window_height;

/* my globals */
static char rcfile[SH_PATH_MAX+1];   /* name of rc file after first open() */           
           

/*
 * "private" shrc functions
 */
FILE *shrc_open(char *alternatefile, char *mode)
{
  char        *home=NULL; 
  static FILE *fp;
  struct stat *fs;
  int          rc,
               fd,
               flags;

  if(strlen(rcfile) == 0) 
  {
    if(alternatefile != NULL) 
    {
      strncpy(rcfile, alternatefile, SH_PATH_MAX);
    }
    else 
    {
      home = getenv("HOME");
      if(home == NULL)
      {
        dbg(DBG_ERROR, "%s%s%s%s",
            "You do not have $HOME set, so I could not find a good place to\n",
            "stick my rc file.  Try using the -f option to specify a path, "
            "or -n to skip\n",
            "it entirely (yuck!)\n");
        return NULL;
      }
      strncpy(rcfile, home, SH_PATH_MAX - strlen(SH_RC_NAME) - 2);
      strcat(rcfile, "/");
      strcat(rcfile, SH_RC_NAME);
    }
  }
  
  /* set flags */
  if(mode[0] == 'r') {
    flags = O_RDONLY;
  }
  else {
    flags = (O_WRONLY | O_TRUNC);
  }

  /* check file; if file doesn't exist, create it */
  if((fs = (struct stat *)malloc(sizeof(struct stat))) == NULL)
  {
    dbg(DBG_ERROR, "shrc_open() couldn't allocate %d bytes!\n",
        sizeof(struct stat));
    return NULL;
  }
  rc = stat(rcfile,fs);
  if(rc == -1) 
  {
    if(errno == ENOENT)
    {
      dbg(DBG_WARN, "%s doesn't exist; creating it..\n", rcfile);
      flags = (flags | O_CREAT);
    }
    else 
    {
      dbg(DBG_ERROR, "There is odd borkenness with %s.  (stat rc=%d)\n",
          rcfile, rc);
      return NULL;
    }
  }
  free(fs);

  if((fd = open(rcfile, flags)) < 0)
  {
#ifndef NO_SYS_ERRLIST    
    dbg(DBG_ERROR, "Couldn't open %s! (errno=%d [%s])\n", 
        rcfile, errno, sys_errlist[errno]);
#else
    dbg(DBG_ERROR, "Couldn't open %s! (errno=%d)\n", rcfile, errno);
#endif    
    return NULL;
  }
  
  /* set permissions if new file */
  if(flags & O_CREAT) 
    if(chmod(rcfile, S_IRUSR | S_IWUSR) < 0)
#ifndef NO_SYS_ERRLIST      
      dbg(DBG_WARN, "Couldn't chmod() new rc file.  (errno=%d [%s])\n",
          errno, sys_errlist[errno]);
#else
      dbg(DBG_WARN, "Couldn't chmod() new rc file, (errno=%d)\n", errno);  
#endif  

  if((fp = fdopen(fd, mode)) == NULL)
  {
#ifndef NO_SYS_ERRLIST    
    dbg(DBG_ERROR, "fdopen() failed: errno=%d (%s)\n",
        errno, sys_errlist[errno]);
#else
    dbg(DBG_ERROR, "fdopen() failed: errno=%d\n", errno);
#endif    
  }
  
  return fp;
}

char *getval(char *string, char token)
{
  static char *ret;
  
  ret = NULL;
  
  if((ret = index(string, token)) == NULL)
    return NULL;
  
  return(++ret);
}

sh_rc shrc_parse(FILE *in)
{
  static char  line[LINE_MAX];
  char        *ptr=NULL, *ptr2=NULL;
  sh_client_t *newhost=NULL;
  int          idx, lineno=0;
  
  while(!feof(in))
  {
next:
    fgets(line, LINE_MAX, in);
    if(feof(in))
    {
      goto end;
    }
    
    lineno++;

    /*
     * kill newlines (fgets() will only give us one at a time)
     */
    if((ptr = index(line, '\n')) != NULL)
      (*ptr) = '\0';
    
    /* 
     * strip leading whitespace and comments
     *
     * (yes, I know I said there were no official provisions for comments,
     *  but I know some random clubie is gonna edit the rc file without
     *  having read my wonderful instructions, so we're just avoiding the
     *  loss of sanity points here.  We can taunt them later when they 
     *  complain about the comments disappearing. ^_^;)
     */
    for(ptr=line; *ptr; ptr++)
    {
      if(ptr[0] == '#')
        goto next;  /* any comment we get at this point is by itself */
      if((ptr[0] != ' ') && (ptr[0] != '\t'))
        break;      /* found non-whitespace, drop below for parsing  */
    }

    /* 
     * host section?  
     * checking it first because it's quick and easy 
     */
    if((*ptr) == '[') {
      /* 
       * new host section; make sure previous host's required args were
       * provided.  (if there *was* a previous host..)
       */
      if(newhost != NULL)
      {
        if(newhost->seticmd == NULL)
        {
          dbg(DBG_ERROR, "RC file entry for %s did not have a command set! %s",
              newhost->hostname, "You have been warned!\n");
        }
        /* other required options as they materialize */
      }
      
      /* initialize a new host record */
      sh_client_new(&idx);
      
      /* read hostname and fill it in */
      if((ptr2 = rindex(line, ']')) == NULL)
      {
        dbg(DBG_WARN, "Malformed host section in rc file (line %d)\n",
            lineno);
      }
      else
      {
        (*ptr2) = '\0';
      }
      sh_client_set_hostname(idx, ++ptr);
      goto next;
    }
    
    /*
     * iterate through keywords..
     *   do host ones first, since they'll occur more often.
     */
    /* seticmd */
    if(strstr(ptr, "seticmd="))
    {
      sh_client_set_seticmd(idx, getval(ptr, '='));
      goto next;
    }
    
    /* autores */
    if(strstr(ptr, "autores="))
    {
      sh_client_set_autores(idx, atoi(getval(ptr, '=')));
      goto next;
    }
    
    /* delay */
    if(strstr(ptr, "delay="))
    {
      sh_client_set_delay(idx, atoi(getval(ptr, '=')));
      goto next;
    }

    /* remove_work (obsolete) */
    if(strstr(ptr, "remove_work="))
    {
      /* do nothing - this option will be dropped at next file write */
      goto next;
    }
   
    /* hostname_column_width */
    if(strstr(ptr, "hostname_column_width="))
    {
      clist_host_width = atoi(getval(ptr, '='));
      if(clist_host_width == 0)
        dbg(DBG_WARN, "rc: Bogus value for hostname_column_width.\n");
      goto next;
    }
    
    /* account_column_width */
    if(strstr(ptr, "account_column_width="))
    {
      clist_account_width = atoi(getval(ptr, '='));
      if(clist_account_width == 0)
        dbg(DBG_WARN, "rc: Bogus value for account_column_width.\n");
      goto next;
    }
    
    /* state_column_width */
    if(strstr(ptr, "state_column_width="))
    {
      clist_state_width = atoi(getval(ptr, '='));
      if(clist_state_width == 0)
        dbg(DBG_WARN, "rc: Bogus value for state_column_width.\n");
      goto next;
    }
   
    /* units_column_width */
    if(strstr(ptr, "units_column_width="))
    {
      clist_units_width = atoi(getval(ptr, '='));
      if(clist_units_width == 0)
        dbg(DBG_WARN, "rc: Bogus value for units_column_width.\n");
      goto next;
    }
    
    /* complete_column_width */
    if(strstr(ptr, "complete_column_width="))
    {
      clist_complete_width = atoi(getval(ptr, '='));
      if(clist_complete_width == 0)
        dbg(DBG_WARN, "Bogus value for complete_column_width.\n");
      goto next;
    }
    
    /* window_width */
    if(strstr(ptr, "window_width="))
    {
      main_window_width = atoi(getval(ptr, '='));
      /* window sizes can be 0 */
      goto next;
    }
    
    /* window_height */
    if(strstr(ptr, "window_height="))
    {
      main_window_height = atoi(getval(ptr, '='));
      /* window sizes can be 0 */
      goto next;
    }

    /*
     * if we get this far, the line is crap.
     */
    dbg(DBG_WARN, "rc: line %d is total smack.\n", lineno);
  
  } /* while !eof */

  fclose(in); 
  
end:

  /* make sure last host had required params set */
  if(newhost != NULL)
  {
    if(newhost->seticmd == NULL)

      dbg(DBG_ERROR, "RC file entry for %s did not have a command set! %s",
          newhost->hostname, "You have been warned!\n");
    /* other required args as they materialize */
  }

  return SH_SUCCESS;
}


/*
 * "public" shrc functions
 */
sh_rc shrc_init(void)
{
  memset((void *)rcfile, 0, SH_PATH_MAX+1);
  return SH_SUCCESS;
}

sh_rc shrc_read(char *alternatefile)
{
  FILE *in;
  
  if((in = shrc_open(alternatefile, "r")) == NULL)
  {
    return SH_ERR_RC;
  }
  return shrc_parse(in);
}

sh_rc shrc_save(void)
{
  FILE        *out;
  int          i;
  sh_client_t *sc;

  /*
   * We *have* to have a filename set by now.  If we don't, then
   * we've been told not to use one.  Return.
   */
  if(strlen(rcfile) == 0)
    return SH_SUCCESS;
  
  if((out = shrc_open(NULL, "w")) == NULL)
  {
    return SH_ERR_RC;
  }
  
  /*
   * save options 
   */
  fprintf(out, 
          "# setiherder options.  You probably don't need to edit this,\n");
  
  /* global options first */
  fprintf(out, "window_width=%d\n",          main_window_width   );
  fprintf(out, "window_height=%d\n",         main_window_height  );
  fprintf(out, "hostname_column_width=%d\n", clist_host_width    );
  fprintf(out, "account_column_width=%d\n",  clist_account_width );
  fprintf(out, "state_column_width=%d\n",    clist_state_width   );
  fprintf(out, "units_column_width=%d\n",    clist_units_width   );
  fprintf(out, "complete_column_width=%d\n", clist_complete_width);

  /* host options */
  for(i=0; i<=sh_client_idx; i++)
  {
    sc = sh_client_get(i);
    fprintf(out, "[%s]\n",             sc->hostname   );
    fprintf(out, "  seticmd=%s\n",     sc->seticmd    );
    fprintf(out, "  autores=%d\n",     sc->autores    );
    fprintf(out, "  delay=%d\n",       sc->delay      );
  }  
  fclose(out);
  
  return SH_SUCCESS;
}




