 /*
  * config.c
  * Copyright (C) Aitor Cuadrado Zubizarreta <aitor@genuen.org>
  * 
  * simple-netaid 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 3 of the License, or
  * (at your option) any later version.
  * 
  * simple-netaid 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/>.
  * 
  * See the COPYING file.
  */

#include "config.h"
#include "ini.h"
#include "def.h"
#include "util.h"

#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include <linux/limits.h>
#include <signal.h>
   
static bool foreground_initialized = false;

/* Allocate and return root/path joined; NULL on error */
static char *get_fullpath(char const *root, char const *path)
{
   char *dest;
   size_t len;
   size_t root_len;
   int n;

   if (!root || !path)
      return NULL;

   root_len = strlen(root);
   len = root_len + strlen(path) + 2;
   dest = malloc(len);
   if (!dest)
      return NULL;

   if (root_len > 0 && root[root_len - 1] == '/' && path[0] == '/')
      n = snprintf(dest, len, "%s%s", root, path + 1);
   else if (root_len > 0 && root[root_len - 1] != '/' && path[0] != '/')
      n = snprintf(dest, len, "%s/%s", root, path);
   else
      n = snprintf(dest, len, "%s%s", root, path);

   if (n < 0 || (size_t)n >= len) {
      free(dest);
      return NULL;
   }

   return dest;
}

static int config_section_ok(const char *section)
{
   if (!section)
      return 0;

   return strcmp(section, CONFIG_NAME) == 0
       || strcmp(section, "snetaid") == 0;
}

static int config_dup_field(char **field, const char *value)
{
   char *dup;

   if (*field || !value)
      return 1;

   dup = strdup(value);
   if (!dup)
      return -1;

   *field = dup;
   return 1;
}

// ini parser callback 
// return 1 on parsed (WARNING: masks OOM)
// return 0 on not parsed
static int config_ini_parser(void* userdata, char const* section, char const* name, char const* value)
{   
   config_t* conf = (config_t*)userdata;
   
   if (config_section_ok(section)) {
      
      if(strcmp(name, "foreground") == 0) {
       
       // The flag --foreground does exist
         if(!foreground_initialized) {
          conf->foreground = atoi(value);
       }
         
         return 1;   
      }
      
      if(strcmp(name, CONFIG_PIDFILE_PATH) == 0) {
        
         return config_dup_field(&conf->pidfile_path, value);
      }
      
      if(strcmp(name, CONFIG_LOGFILE_PATH) == 0) {
         
         return config_dup_field(&conf->logfile_path, value);
      }
      
      if(strcmp(name, CONFIG_LOCKFILE_PATH) == 0) {
         
         return config_dup_field(&conf->lockfile_path, value);
      }
      
      if(strcmp(name, CONFIG_LOG_LEVEL) == 0) {
         
         if(strcasecmp(value, "debug") == 0) {
            
            conf->debug_level = LOGLEVEL_DEBUG;
            conf->error_level = LOGLEVEL_WARN;
         }
         else if(strcasecmp(value, "info") == 0) {
            
            conf->debug_level = LOGLEVEL_INFO;
            conf->error_level = LOGLEVEL_WARN;
         }
         else if(strcasecmp(value, "warn") == 0 || strcasecmp(value, "warning") == 0) {
            
            conf->debug_level = LOGLEVEL_NONE;
            conf->error_level = LOGLEVEL_WARN;
         }
         else if(strcasecmp(value, "error") == 0 || strcasecmp(value, "critical") == 0) {
            
            conf->debug_level = LOGLEVEL_NONE;
            conf->error_level = LOGLEVEL_ERROR;
         }
         else {
            
            fprintf(stderr, "Unrecognized value '%s' for '%s'\n", value, name);
            return 0;
         }
         
         return 1;
      }
      
      if(strcmp(name, CONFIG_DEFAULT_WIRED_IF) == 0) {
        
         return config_dup_field(&conf->wired_interface, value);
      }
      
      if(strcmp(name, CONFIG_DEFAULT_WIRELESS_IF) == 0) {
        
         return config_dup_field(&conf->wireless_interface, value);
      }

      fprintf(stderr, "Unrecognized field '%s' in section [%s]\n", name, section);
      return 0;
   }

   fprintf(stderr, "Unrecognized section '[%s]' (expected [%s])\n", section, CONFIG_NAME);
   return 0;
}   

// initialize a config 
// always succeeds
int config_init(config_t* conf)
{   
   memset(conf, 0, sizeof(config_t));
   return 0;
}

// load from a file, by path
// return on on success
// return -errno on failure to open 
int config_load(char const* path, config_t* conf)
{   
   FILE* f = NULL;
   int rc = 0;
   
   f = fopen(path, "r");
   if(f == NULL) {
      rc = -errno;
      return rc;
   }
   
   rc = config_load_file(f, conf);
   
   fclose(f);
   
   if(rc == 0) {
      
      //config_make_instance_nonce(conf);
   }
   return rc;
}

// load from a file
// return 0 on success
// return -errno on failure to load
int config_load_file(FILE* file, config_t* conf)
{
   int rc = 0;
   
   rc = ini_parse_file(file, config_ini_parser, conf);
   if(rc != 0) {
      log_error("ini_parse_file(config) rc = %d\n", rc);
      config_free(conf);

      return rc;
   }

   // convert paths 
   rc = config_fullpaths(conf);
   if(rc != 0) {

      log_error("config_fullpaths: %s\n", strerror(-rc));
      config_free(conf);
      return rc;
   }
   
   return rc;
}

// free a config
// always succeeds
int config_free(config_t* conf)
{   
   if(conf->config_path != NULL) {
      
      free(conf->config_path);
      conf->config_path = NULL;
   }
   
   
   if(conf->logfile_path != NULL) {
      
      free(conf->logfile_path);
      conf->logfile_path = NULL;
   }   
   
   if(conf->lockfile_path != NULL) {
      
      free(conf->lockfile_path);
      conf->lockfile_path = NULL;
   }
   
   if(conf->pidfile_path != NULL) {
      
      free(conf->pidfile_path);
      conf->pidfile_path = NULL;
   }
   
   if(conf->wired_interface != NULL) {
      
      free(conf->wired_interface);
      conf->wired_interface = NULL;
   }
   
   if(conf->wireless_interface != NULL) {
      
      free(conf->wireless_interface);
      conf->wireless_interface = NULL;
   }
   
   return 0;
}

// convert all paths in the config to absolute paths 
// return 0 on success 
// return -ENOMEM on OOM 
// return -ERANGE if cwd is too long
int config_fullpaths(config_t* conf)
{   
   char** need_fullpath[] = {
      &conf->config_path,
      &conf->pidfile_path,
      &conf->logfile_path,
      &conf->lockfile_path,
      NULL
   };
   
   char cwd_buf[ PATH_MAX + 1 ];
   memset(cwd_buf, 0, PATH_MAX + 1);
   
   char* tmp = getcwd(cwd_buf, PATH_MAX);
   if(tmp == NULL) {
      
      log_error("Current working directory exceeds %u bytes\n", PATH_MAX);
      return -ERANGE;
   }
   
   for(int i = 0; need_fullpath[i] != NULL; i++) {
      
      if(need_fullpath[i] != NULL && (*need_fullpath[i]) != NULL) {
         
         if(*(need_fullpath[i])[0] != '/') {
               
            // relative path
            char* new_path = get_fullpath(cwd_buf, *need_fullpath[i]);
            if(new_path == NULL)               
               return -ENOMEM;
            
            free(*(need_fullpath[i]));
            *(need_fullpath[i]) = new_path;
         }
      }
   }
   
   return 0;
}


// print usage statement 
int config_usage(char const* progname)
{
   fprintf(stderr, "%s -- Simple Netaid Daemon\n\n"
   "Usage: %s [options]\n\n"
      "Options:\n"
      "   -a --conf_file filename   Read configuration from the file\n\n"
      "   -l --log_file  filename   Write logs to the file\n\n"
      "   -v --verbose-level        VERBOSITY\n\
                                    Set the level of verbose output.  Valid values are\n\
                                    positive integers.  Larger integers lead to more\n\
                                    debugging information.\n\n"
      "   -p --pid_file  filename   PID file used by daemonized app\n\n"
      "   -b --background           Run in the background\n\n"
      "   -f --foreground           Run in the foreground (Default)\n\n"
      "   -e --wired_if             Default wired interface\n\n"
      "   -w --wireless_if          Default wireless interface\n\n"
      "   -s --stop                 Stop the service once the connection has been stablished\n\n"
      "   -h --help                 Show this help\n\n"
      "   -k --kill                 Kill a running daemon\n\n"
      "   -c --check                Check whether an instance is already running\n\n",
      progname, progname);
  
  return 0;
}


// parse command-line options from argv.
// config must be initialized; this method simply augments it 
// return 0 on success 
// return -1 on unrecognized option 
static int config_set_string(char **dest, const char *value)
{
   char *dup;

   if (!dest)
      return -1;

   dup = value ? strdup(value) : NULL;
   if (value && !dup)
      return -1;

   free(*dest);
   *dest = dup;
   return 0;
}

int config_load_from_args(config_t* config, int argc, char** argv)
{
   config_t *cfg;

   if (!config)
      return -1;

   cfg = config;

   static struct option long_options[] = {
      // This option sets a flag.
      //{"daemon",               no_argument,  &daemonized, 1},
      // These other options don’t set a flag. We distinguish them by their indices.
      {"background",            no_argument, 0, 'b'},
      {"foreground",            no_argument, 0, 'f'},
      {"kill",                  no_argument, 0, 'k'},
      {"stop",                  no_argument, 0, 's'},
      {"verbose-level",         required_argument, 0, 'v'},
      {"config_path",           required_argument, 0, 'a'},
      {"logfile_path",          required_argument, 0, 'l'},
      {"pidfile_path",          required_argument, 0, 'p'},
      {"wired_interface",      required_argument, 0, 'e'},
      {"wireless_interface",   required_argument, 0, 'w'},
      {"check",         no_argument, 0, 'c'},
      {0, 0, 0, 0}
   };
   
   /* Try to process all command line arguments */
   // Si la letra del argumento va seguida de : entonces es que requiere argumento,
   // si va seguida de :: entonces el argumento es opcional. El orden es irrelevante.
   // Podría ser, por ejemplo:  "a:bfkl:v:cp:"
   int rc = 0;
   int value, option_index = 0;
   cfg->action_kill = false;
   cfg->action_check = false;
   while ((value = getopt_long(argc, argv, "e:w:fbka:l:v:p:c", long_options, &option_index)) != -1) {
      switch (value) {
         case 0:
            if (long_options[option_index].flag)
            break;
            
         case 'b':
            cfg->foreground = false;
            foreground_initialized = true;
            break;
            
         case 'f':
            cfg->foreground = true;
            foreground_initialized = true;
            break;
            
         case 'a':
            if (config_set_string(&cfg->config_path, optarg) < 0)
               rc = -1;
            break;
                
         case 'l':
            if (config_set_string(&cfg->logfile_path, optarg) < 0)
               rc = -1;
            break;
            
         case 'e':
            if (config_set_string(&cfg->wired_interface, optarg) < 0)
               rc = -1;
            break;
            
         case 'w':
            if (config_set_string(&cfg->wireless_interface, optarg) < 0)
               rc = -1;
            break;
         
         case 'v':
            {
               long debug_level = 0;
               char* tmp = NULL;
            
               debug_level = strtol(optarg, &tmp, 10);
            
               if(*tmp != '\0') {
                  fprintf(stderr, "Invalid argument for -d\n");
                  rc = -1;
               }
               else {
                  cfg->debug_level = debug_level;  
               }
            }
            break;
                
         case 'p':
            if (config_set_string(&cfg->pidfile_path, optarg) < 0)
               rc = -1;
            break;
                
         case 'k':
            cfg->action_kill = true;
            break;
            
         case 'c':
            cfg->action_check = true;
            break;
                  
         default:
            syslog(LOG_ERR, "Unknown parameter.");
            rc = -1;
            break;
      }
      
   }  // while
   
   return rc;
}

