/* pref.c read, write, and manipulate preferences */
/* markus@mhoenicka.de 2-8-00 */
/* $Id: pref.c,v 1.15.2.2 2005/08/06 22:44:09 mhoenicka Exp $ */

/*
   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, see <http://www.gnu.org/licenses/>

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/


#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include <ctype.h> /* for isdigit() */

#include "refdb.h"
#include "linklist.h"
#include "pref.h"
#include "strfncs.h"

/* global variables */
extern char confdir[];

/* forward declarations of local functions */
static int read_data(FILE* fp, Prefs* ptr_prefs);
static int read_nsf_data(FILE* fp, Lilibib* ptr_prefs);

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  read_prefs(): reads preferences from a configuration file                    
                The configuration file format is simple: Each line
                contains a pair of variable name and variable value,
                separated by whitespace. A # outcomments everything to
                the right.
	        A variable name may have up to 16 characters, the
		value may have up to 255 characters (_POSIX_PATH_MAX)
		read_prefs() first looks for <app>rc in /etc, and then in
		$REFDBLIB if /etc didn't contain a suitable file, and
		sets any variables it finds. Then it looks for .<app>rc
		or <app>rc (in this order) in $HOME and reads the
		variables from the first file it finds. Values in $HOME
                override the values in /etc or $REFDBLIB

  int read_prefs returns 0 if at least one init file is found, 1 if no
                init file is found. It is upon the discretion of the
		calling function whether this is an error or not.

  void* ptr_prefs ptr to an array of Prefs structures. The last Pref
                structure must have a zero-length varname string
		which acts as a terminator
 
  char *init_file ptr to a string with the name of the init file.
                  This is first looked up in /etc, then $REFDBLIB,
                  then in $HOME

  int type 0 = regular configuration data. ptr_prefs will be cast to Prefs*,
           numOpts must be set to the size of the array pointed to by
	   ptr_prefs
	   1 = non-standard field mapping. ptr_prefs will be cast to
           Lilibib*, numOpts should be set to 0.

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int read_prefs(void* ptr_prefs, char* init_file, int type) {
  FILE *fp;
  char *home; /* value of the HOME environment variable */
  char *refdblib; /* value of the REFDBLIB environment variable */
  char default_refdblib[_POSIX_PATH_MAX+1] = SYSCONFDIR; /* usually /etc */
  char theinitfile[_POSIX_PATH_MAX+1];
  char have_global = 0;

  home = getenv("HOME");

  /* confdir is set through a command line option and overrides the
     compile-time default */
  refdblib = (*confdir) ? confdir : default_refdblib;

  /* first try  to read the global file */
  snprintf(theinitfile, _POSIX_PATH_MAX, "%s/%s", refdblib, init_file);
  fp = fopen(theinitfile, "r");
  if (fp != NULL) {
    have_global = 1;
    if (!type) {
      read_data(fp, (Prefs*)ptr_prefs);
    }
    else {
      read_nsf_data(fp, (Lilibib*)ptr_prefs);
    }
    fclose(fp);
  }
  else {
    /* fallback: look in $REFDBLIB */
    refdblib = getenv("REFDBLIB");
    if (refdblib != NULL) {
      snprintf(theinitfile, _POSIX_PATH_MAX, "%s/%s", refdblib, init_file);
      fp = fopen(theinitfile, "r");
      if (fp != NULL) {
	have_global = 1;
	/*        fprintf(stderr, "use config file %s\n", theinitfile); */
	if (!type) {
	  read_data(fp, (Prefs*)ptr_prefs);
	}
	else {
	  read_nsf_data(fp, (Lilibib*)ptr_prefs);
	}
	fclose(fp);
      }
    }
  }

/*    if (!have_global) { */
/*      fprintf(stderr, "no global config file found\n"); */
/*    } */

  /* now try to read the settings in $HOME/.rc */
  if (home == NULL) {    /* if HOME is not set, try the current directory */
    fp = fopen(init_file, "r");
    if (fp == NULL) {
      return ((have_global) ? 0:1);
    }
  }
  else {
    snprintf(theinitfile, _POSIX_PATH_MAX, "%s/.%s", home, init_file);
    fp = fopen(theinitfile, "r");
    if (fp == NULL) {    /* if not present, try a non-hidden rc file */
      snprintf(theinitfile, _POSIX_PATH_MAX, "%s/%s", home, init_file);
      fp = fopen(theinitfile, "r");
      if (fp == NULL) {    /* if not present in HOME, we're done */
	return ((have_global) ? 0:1);
      }
    }
  }

  if (!type) {
    read_data(fp, (Prefs*)ptr_prefs);
  }
  else {
    read_nsf_data(fp, (Lilibib*)ptr_prefs);
  }

  fclose(fp);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  read_data(): reads data from an init file                    

  static int read_data returns 0 (no error checking yet)

  FILE* fp ptr to an open stream with the data to analyze

  Prefs* ptr_prefs ptr to an array of Prefs structures
 
  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int read_data(FILE* fp, Prefs* ptr_prefs) {
  char line [PREFS_BUF_LEN]; /* acommodates a variable name of max.
				     16 chars, a tab, a POSIX full path,
				     and a \0 */
  char *name_token;
  char *value_token;
  int i;
  char emtpy_string[1] = "";

  line[0] = '\0';

  while (fgets(line, PREFS_BUF_LEN, fp) != NULL) {
    /* discard comment lines right away*/
    if (line[0] == '#') {
      continue;
    }
    
    line[PREFS_BUF_LEN-1] = '\0';
    name_token = strtok(line, " \t\r\n#");
    if (name_token == NULL || !name_token[0]) {
      continue; /* emtpy line */
    }
    
    /* check also for # in value token, so we can outcomment values to get
       the default */
    value_token = strtok(NULL, " \t\r\n#");
    if (value_token == NULL) {
      value_token = emtpy_string; /* no value, assign empty string */
    }

    /* loop over all variable names and see whether the config file line
       specifies one of the names */
    i = 0;
    while (*(ptr_prefs[i].varname)) {
      if (strcmp(ptr_prefs[i].varname, name_token) == 0) {
	strcpy(ptr_prefs[i].varvalue, value_token);
	ptr_prefs[i].varvalue[PREFS_BUF_LEN-1] = '\0';
	break;
      }
      i++;
    }
  }
  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  read_nsf_data(): reads non-standard field data from an init file            

  static int read_nsf_data returns 0 if ok, 1 if memory error

  FILE* fp ptr to an open stream with the data to analyze

  Lilibib* ptr_prefs ptr to linked list for storing data

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static int read_nsf_data(FILE* fp, Lilibib* ptr_prefs) {
  char* line;
  char *name_token;
  char *value_token;
  char emtpy_string[1] = "";

  line = malloc(NSF_LINE_LEN);
  if (line == NULL) {
    return 1;
  }

  while (fgets(line, NSF_LINE_LEN, fp) != NULL) {
    /* discard comment lines right away*/
    if (line[0] == '#') {
      continue;
    }
    
/*      line[PREFS_BUF_LEN-1] = '\0'; */
    /* ignore lines that do not start with "nsf_" */
    if (strncmp(line, "nsf_", 4)) {
      continue;
    }

    name_token = strtok(line, " \t\r\n#");
    if (name_token == NULL || !name_token[0]) {
      continue; /* emtpy line */
    }
    
    /* check also for # in value token, so we can outcomment values to get
       the default */
    value_token = strtok(NULL, " \t\r\n#");
    if (value_token == NULL) {
      value_token = emtpy_string; /* no value, assign empty string */
    }
    /* strip the "nsf_" prefix when adding the name/value pair to the list */
    if (insert_lilibib(ptr_prefs, name_token+4, value_token)) {
      free(line);
      return 1;
    }
  }    

  free(line);

  return 0;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  num_loglevel(): converts the log_level string to a number

  int num_loglevel returns the log level or -1 if log_level was junk

  char* log_level ptr to a string containing the log level as a digit
                  or as prose

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int num_loglevel(char* log_level) {
  char buffer[] = {'\0','\0'};

  if (isdigit((int)log_level[0])) {
    buffer[0] = log_level[0]; /* use only first digit to avoid surprises */
    return atoi(buffer);
  }
  else {
    strup(log_level);
    if (!strcmp(log_level, "EMERG")) {
      return 0;
    }
    else if (!strcmp(log_level, "ALERT")) {
      return 1;
    }
    else if (!strcmp(log_level, "CRIT")) {
      return 2;
    }
    else if (!strcmp(log_level, "ERR")) {
      return 3;
    }
    else if (!strcmp(log_level, "WARNING")) {
      return 4;
    }
    else if (!strcmp(log_level, "NOTICE")) {
      return 5;
    }
    else if (!strcmp(log_level, "INFO")) {
      return 6;
    }
    else if (!strcmp(log_level, "DEBUG")) {
      return 7;
    }
  }
  return -1; /* switch off logging if the string was junk */
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  num_logdest(): converts the log_dest string to a number

  int num_logdest returns the log destination. If log_level is junk,
                  syslog (=1) is assumed.

  char* log_dest ptr to a string containing the log destination as a
                  digit or as prose

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
int num_logdest(char* log_dest) {
  char buffer[] = {'\0','\0'};

  if (isdigit((int)log_dest[0])) {
    buffer[0] = log_dest[0]; /* use only first digit to avoid surprises */
    return atoi(buffer);
  }
  else {
    strup(log_dest);
    if (!strcmp(log_dest, "STDERR")) {
      return 0;
    }
    else if (!strcmp(log_dest, "SYSLOG")) {
      return 1;
    }
    else if (!strcmp(log_dest, "FILE")) {
      return 2;
    }
  }
  return 1; /* use syslog as default */
}

