/*
 *   NVRAM WakeUp
 *   Copyright (C) 2001-2004, Sergei Haller.
 *
 *   $Id: readconf.c 926 2010-01-24 11:53:49Z tiber $
 *
 *   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
 *
 */

#define CVSREV_readconf_c \
     "$Id: readconf.c 926 2010-01-24 11:53:49Z tiber $"

/*
 *  
 *  The syntax of the configuration file is the following:
 *
 *    - lines containing no non-space characters are comments.
 *
 *    - If there's a '#' somewhere on the line, the rest of the line
 *      is a comment.
 *
 *    - lines of the form  VARIABLE = VALUE  with optional following
 *      comment  are  interpreted  as  a  definition of the variable
 *      VARIABLE to the value VALUE.  
 *
 *  Read `man nvram-wakeup.conf(5)' for the list on allowed variable 
 *  names
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <syslog.h>

#include "nvram-wakeup.h"

struct biosinfo lnrs; /* store line numbers for different variables */

void deprecated(char * old, char * new) {

     nvprintf(LOG_WARNING, "Use of \"%s\" is deprecated. Use \"%s\" instead.\n", old, new);

}

int assign(int lnr, struct biosinfo * b, int * ptr, char * val, int allow_symbolic) {
     char * tail;

     /* 
      * now is the tricky part:
      * pointer to the member of lnrs corresponding 
      * to the member pointed by ptr in *b
      *          I love C
      */
     int  * prev = (int *)((int)(&lnrs) + ((int)ptr - (int)b));

     if (*prev) {
          return *prev; 
     }
     else {
          *prev = lnr;
          switch (allow_symbolic) {
          case 0: /* no symbolic assignments allowed */
                  break;
          case 1: /* only ON/OFF allowed */
                  if      (!strcmp(val, "ON"            )) {*ptr  = ON            ;                                        return 0;}
                  else if (!strcmp(val, "OFF"           )) {*ptr  = OFF           ;                                        return 0;}
                  break;
          case 2: /* ON_STAT and stuff like this */
                  if      (!strcmp(val, "ALWAYS"        )) {*ptr  = ALWAYS        ; deprecated("ALWAYS", "ON_ANY_CHANGE"); return 0;}
                  else if (!strcmp(val, "ON_ANY_CHANGE" )) {*ptr  = ON_ANY_CHANGE ;                                        return 0;}
                  else if (!strcmp(val, "ON_STAT"       )) {*ptr  = ON_STAT       ;                                        return 0;}
                  else if (!strcmp(val, "ON_MON"        )) {*ptr  = ON_MON        ;                                        return 0;}
                  else if (!strcmp(val, "ON_DAY"        )) {*ptr  = ON_DAY        ;                                        return 0;}
                  else if (!strcmp(val, "ON_WDAYS"      )) {*ptr  = ON_WDAYS      ;                                        return 0;}
                  else if (!strcmp(val, "ON_HOUR"       )) {*ptr  = ON_HOUR       ;                                        return 0;}
                  else if (!strcmp(val, "ON_MIN"        )) {*ptr  = ON_MIN        ;                                        return 0;}
                  else if (!strcmp(val, "ON_SEC"        )) {*ptr  = ON_SEC        ;                                        return 0;}
                  else if (!strcmp(val, "OFF"           )) {*ptr  = OFF           ;                                        return 0;}
                  /* ... more to come ??? ... */
                  break;
          case 3: /* upper methods */
                  if      (!strcmp(val, "OFF"           )) {*ptr  = OFF           ;                                        return 0;}
                  else if (!strcmp(val, "INTEL"         )) {*ptr  = INTEL         ;                                        return 0;}
                  else if (!strcmp(val, "DS1685"        )) {*ptr  = DS1685        ;                                        return 0;}
                  else if (!strcmp(val, "VT82Cxxx"      )) {*ptr  = VT82Cxxx      ;                                        return 0;}
                  else if (!strcmp(val, "VT8235_37"     )) {*ptr  = VT8235_37     ;                                        return 0;}
                  /* ... more to come ??? ... */
                  break;
          case 4: /* checksum algorithms */
                  if      (!strcmp(val, "DELL"          )) {*ptr  = DELL          ;                                        return 0;}
                  else if (!strcmp(val, "FSC"           )) {*ptr  = FSC           ;                                        return 0;}
                  /* ... more to come ??? ... */
                  break;
          }

          errno = 0;
          *ptr = strtol(val, &tail, 0);

          if (*tail != 0) return -2; /* val is not a number */
          if (errno)      return -3; /* overflow (number too large/too small) */
                          return  0; /* ok */
     }
}

int readconf(struct biosinfo *b, const char * filename) {

     char *    line=NULL;
     char *    rest=NULL;
     char *    comment=NULL;
     char *    name=NULL;
     char *    val=NULL;
     size_t    n = 0;
     int       lnr = 0; /* number of the line */
     ssize_t   got;
     FILE *    fd_conf;

     if (!set_biosinfo_defaults(b)) {
          nvprintf(LOG_ERR, "Can't set biosinfo defaults\n");
          return 0;
     }
     
     nvprintf(LOG_DEBUG, "Opening %s in 'r' mode...\n", filename);
     if ((fd_conf = fopen(filename, "r")) == NULL) {
          nvprintf(LOG_ERR, "%s: %m\n", filename);
          return 0;
     }
     
     /* read the next line */
     while ((got = getline(&line,&n,fd_conf)) > -1) {
          lnr++;
/*
 *        printf("got %d characters\n",got);
 */

          /* copy contents of the line to be used with strsep */
          comment = strdupa(line);

          /* release space allocated by getline and reset n */
          free(line);
          n=0;
          
          /* everything after a # is a comment */
          rest = strip(strsep(&comment, "#"));
         
          /* remove trailing line break */
          if (comment && comment[strlen(comment)-1] == '\n')
               comment[strlen(comment)-1] = 0;

          if (comment)
               nvprintf(LOG_DEBUG, "%s: %d: comment: #%s\n", filename, lnr, comment);

          if (rest && *rest) {
          
               /* split the rest into a pair of name and value */
               name = strip(strsep(&rest, "="));
               val  = strip(rest);
               nvprintf(LOG_DEBUG, "%s: %d: name   : %s\n", filename, lnr, name   );
               nvprintf(LOG_DEBUG, "%s: %d: value  : %s\n", filename, lnr, val    );
          
               if (name && *name && val && *val) {
                    int ret;

                    /* now assign values */
                         if (!strcmp(name, "need_reboot"     ))    ret = assign(lnr, b, &(b->need_reboot     ), val, 2);
                    else if (!strcmp(name, "addr_chk_h"      ))    ret = assign(lnr, b, &(b->addr_chk_h      ), val, 0);
                    else if (!strcmp(name, "addr_chk_l"      ))    ret = assign(lnr, b, &(b->addr_chk_l      ), val, 0);
                    else if (!strcmp(name, "addr_chk_h2"     ))    ret = assign(lnr, b, &(b->addr_chk_h2     ), val, 0);
                    else if (!strcmp(name, "addr_chk_l2"     ))    ret = assign(lnr, b, &(b->addr_chk_l2     ), val, 0);
                    else if (!strcmp(name, "addr_stat"       ))    ret = assign(lnr, b, &(b->addr_stat       ), val, 0);
                    else if (!strcmp(name, "addr_mon"        ))    ret = assign(lnr, b, &(b->addr_mon        ), val, 0);
                    else if (!strcmp(name, "addr_day"        ))    ret = assign(lnr, b, &(b->addr_day        ), val, 0);
                    else if (!strcmp(name, "addr_wdays"      ))    ret = assign(lnr, b, &(b->addr_wdays      ), val, 0);
                    else if (!strcmp(name, "addr_hour"       ))    ret = assign(lnr, b, &(b->addr_hour       ), val, 0);
                    else if (!strcmp(name, "addr_min"        ))    ret = assign(lnr, b, &(b->addr_min        ), val, 0);
                    else if (!strcmp(name, "addr_sec"        ))    ret = assign(lnr, b, &(b->addr_sec        ), val, 0);
                    else if (!strcmp(name, "shift_stat"      ))    ret = assign(lnr, b, &(b->shift_stat      ), val, 0);
                    else if (!strcmp(name, "shift_mon"       ))    ret = assign(lnr, b, &(b->shift_mon       ), val, 0);
                    else if (!strcmp(name, "shift_day"       ))    ret = assign(lnr, b, &(b->shift_day       ), val, 0);
                    else if (!strcmp(name, "shift_wdays"     ))    ret = assign(lnr, b, &(b->shift_wdays     ), val, 0);
                    else if (!strcmp(name, "shift_hour"      ))    ret = assign(lnr, b, &(b->shift_hour      ), val, 0);
                    else if (!strcmp(name, "shift_min"       ))    ret = assign(lnr, b, &(b->shift_min       ), val, 0);
                    else if (!strcmp(name, "shift_sec"       ))    ret = assign(lnr, b, &(b->shift_sec       ), val, 0);
                    else if (!strcmp(name, "rtc_time"        ))    ret = assign(lnr, b, &(b->rtc_time        ), val, 1);
                    else if (!strcmp(name, "rtc_day"         ))    ret = assign(lnr, b, &(b->rtc_day         ), val, 0);
                    else if (!strcmp(name, "rtc_mon"         ))    ret = assign(lnr, b, &(b->rtc_mon         ), val, 0);
                    else if (!strcmp(name, "rtc_day_0_is_c0" ))    ret = assign(lnr, b, &(b->rtc_day_0_is_c0 ), val, 1);
                    else if (!strcmp(name, "rtc_mon_0_is_c0" ))    ret = assign(lnr, b, &(b->rtc_mon_0_is_c0 ), val, 1);
                    else if (!strcmp(name, "reset_day"       ))    ret = assign(lnr, b, &(b->reset_day       ), val, 1);
                    else if (!strcmp(name, "reset_mon"       ))    ret = assign(lnr, b, &(b->reset_mon       ), val, 1);
                    else if (!strcmp(name, "nr_stat"         ))    ret = assign(lnr, b, &(b->nr_stat         ), val, 0);
                    else if (!strcmp(name, "nr_mon"          ))    ret = assign(lnr, b, &(b->nr_mon          ), val, 0);
                    else if (!strcmp(name, "nr_day"          ))    ret = assign(lnr, b, &(b->nr_day          ), val, 0); 
                    else if (!strcmp(name, "nr_hour"         ))    ret = assign(lnr, b, &(b->nr_hour         ), val, 0);
                    else if (!strcmp(name, "nr_min"          ))    ret = assign(lnr, b, &(b->nr_min          ), val, 0);
                    else if (!strcmp(name, "nr_sec"          ))    ret = assign(lnr, b, &(b->nr_sec          ), val, 0);
                    else if (!strcmp(name, "nr_rtc_day"      ))    ret = assign(lnr, b, &(b->nr_rtc_day      ), val, 0);
                    else if (!strcmp(name, "nr_rtc_mon"      ))    ret = assign(lnr, b, &(b->nr_rtc_mon      ), val, 0);
                    else if (!strcmp(name, "nr_wdays"        ))    ret = assign(lnr, b, &(b->nr_wdays        ), val, 0);
                    else if (!strcmp(name, "bcd"             ))    ret = assign(lnr, b, &(b->bcd             ), val, 1);
                    else if (!strcmp(name, "day_no_bcd"      ))    ret = assign(lnr, b, &(b->day_no_bcd      ), val, 1);
                    else if (!strcmp(name, "day_hack"        ))    ret = assign(lnr, b, &(b->day_hack        ), val, 0);
                    else if (!strcmp(name, "upper_method"    ))    ret = assign(lnr, b, &(b->upper_method    ), val, 3);
                    else if (!strcmp(name, "chk_method"      ))    ret = assign(lnr, b, &(b->chk_method      ), val, 4);

                    else if (!strcmp(name, "addr_date"       ))  { deprecated("addr_date",          "addr_day"        );
                                                                   ret = assign(lnr, b, &(b->addr_day        ), val, 0); }
                    else if (!strcmp(name, "shift_date"      ))  { deprecated("shift_date",         "shift_day"       );
                                                                   ret = assign(lnr, b, &(b->shift_day       ), val, 0); }
                    else if (!strcmp(name, "rtc_date"        ))  { deprecated("rtc_date",           "rtc_day"         );
                                                                   ret = assign(lnr, b, &(b->rtc_day         ), val, 0); }
                    else if (!strcmp(name, "rtc_date_0_is_c0"))  { deprecated("rtc_date_0_is_c0",   "rtc_day_0_is_c0" );
                                                                   ret = assign(lnr, b, &(b->rtc_day_0_is_c0 ), val, 1); }
                    else if (!strcmp(name, "reset_date"      ))  { deprecated("reset_date",         "reset_day"       );
                                                                   ret = assign(lnr, b, &(b->reset_day       ), val, 1); }
                    else if (!strcmp(name, "nr_date"         ))  { deprecated("nr_date",            "nr_day"          );
                                                                   ret = assign(lnr, b, &(b->nr_day          ), val, 0); }
                    else if (!strcmp(name, "nr_rtc_date"     ))  { deprecated("nr_rtc_date",        "nr_rtc_day"      );
                                                                   ret = assign(lnr, b, &(b->nr_rtc_day      ), val, 0); }
                    else if (!strcmp(name, "date_hack"       ))  { deprecated("date_hack",          "day_hack"        );
                                                                   ret = assign(lnr, b, &(b->day_hack        ), val, 0); }

                    else ret = -1;

                    switch(ret) {
                         case  -1: nvprintf(LOG_ERR, "%s: %d: Syntax error: unknown variable name\n", filename, lnr);
                                   fclose(fd_conf);
                                   return 0;
                         case  -2: nvprintf(LOG_ERR, "%s: %d: Syntax error: value is not a number\n", filename, lnr);
                                   fclose(fd_conf);
                                   return 0;
                         case  -3: nvprintf(LOG_ERR, "%s: %d: Syntax error: value too large/too small\n", filename, lnr);
                                   fclose(fd_conf);
                                   return 0;
                         case   0: break;
                         default : if (ret>0) {
                                        nvprintf(LOG_ERR, "%s: %d: Previous definition of variable `%s': first definition in line %d\n", 
                                                           filename, lnr, name, ret);
                                        fclose(fd_conf);
                                        return 0;
                                   }
                                   /* should have no negative numbers here */
                    }

               }
               else {
                    nvprintf(LOG_ERR,   "%s: %d: Syntax error: no value\n", filename, lnr);
                    fclose(fd_conf);
                    return 0;
               }
          }
     }
     
     nvprintf(LOG_DEBUG, "Closing %s ...\n", filename);
     fclose(fd_conf);
     return 1;
}

