/*
 * rcfiles.c: useful functions to deal with parameters from rc files
 * or command line
 *
 * Copyright (C)
 *   2002 - 2012  Arnaud Quette <arnaud.quette@free.fr>
 *   2022 - 2025  Jim Klimov <jimklimov+nut@gmail.com>
 *
 * 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, 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 (see the file COPYING); if not, write to the
 * Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
 *
 */

#include "wmnut-common.h"	/* includes config.h */
#include "wmgeneral.h"

#ifdef HAVE_GETOPT_LONG
# include <getopt.h>
#endif

#if (defined HAVE_REALPATH) && HAVE_REALPATH
# ifdef HAVE_LIMITS_H
#  include <limits.h>
# endif
# ifdef HAVE_STDLIB_H
#  include <stdlib.h>
# endif
#endif

/*******************************************************************************\
|* AddRcKey                                                                     |
\*******************************************************************************/
void AddRcKey(rckeys *key, const char *label, int type, void *var) {

	if (label != NULL) {
#if (defined HAVE_XMALLOC) && HAVE_XMALLOC
		key->label = (char *)xmalloc(strlen(label) + 1);
#else
		key->label = (char *)malloc(strlen(label) + 1);
#endif	/* HAVE_XMALLOC */
		strcpy(key->label, label);
	}
	else
		key->label = NULL;

	key->type = type;

	switch(type) {
	case TYPE_STRING :
		key->var.str = (char *)var;
		break;
	case TYPE_BOOL:
	case TYPE_INT:
		key->var.integer = (int *)var;
		break;
	case TYPE_FLOAT:
		key->var.floater = (float *)var;
		break;
	case TYPE_NULL:
		break;
	}
}

/*******************************************************************************\
|* FreeRcKeyData                                                                |
\*******************************************************************************/
void FreeRcKeyData(rckeys *key) {
	if (!key)
		return;

	if (key->label) {
		free(key->label);
		key->label = NULL;
	}

	if (key->type == TYPE_STRING && key->var.str) {
		free(key->var.str);
		key->var.str = NULL;
	}
}

/*******************************************************************************\
|* FreeRcKeys                                                                   |
\*******************************************************************************/
void FreeRcKeys(rckeys *keys) {
	/* NOTE: Assumes it accepts an array to free() its elements;
	 * if this array itself is dynamic, caller should free() it */
	int	keynum;

	if (!keys)
		return;

	for (keynum = 0; keys[keynum].type != TYPE_NULL; keynum++) {
		FreeRcKeyData(&keys[keynum]);
	}
}

/*******************************************************************************\
|* ParseRCFile                                                                  |
\*******************************************************************************/
void ParseRCFile(const char *filename, rckeys *keys)
{
	char	*p, *tmp;
	char	temp[128];
	char	*tokens = " =\t\n#";
	FILE	*fp;
	int	key;

	fp = fopen(filename, "r");
	if (fp) {
#ifdef DEBUG
		printf("Opening rc file %s\n", filename);
#endif
		while (fgets(temp, sizeof(temp), fp)) {
			if (temp[0] != '#') {
				key = 0;
				while (key >= 0 && keys[key].label) {
					if ((p = strstr(&temp[0], keys[key].label))) {
						/* jump to "=" */
						p += strlen(keys[key].label);
						/* suppress tokens before value */
						p += strspn(p, tokens);
						/* suppress tokens after value */
						tmp = strpbrk(p, tokens);
						*tmp = '\0';
#ifdef DEBUG
						printf("Parameter -> %s%s\tValue -> %s\n", keys[key].label,
							((strlen(keys[key].label) <12)?"\t\t":"\t"), p);
#endif
						switch(keys[key].type)
						{
							case TYPE_STRING:
#if (defined HAVE_XMALLOC) && HAVE_XMALLOC
								if (keys[key].var.str != NULL)
									free(keys[key].var.str);
								keys[key].var.str = (char *)xmalloc(strlen(p) + 1);
								strncpy(keys[key].var.str, p, strlen(p));
#endif	/* HAVE_XMALLOC */
								if (!strcmp(keys[key].label, "UPS"))
									AddHost(p);
								break;
							case TYPE_BOOL:
								*keys[key].var.boolean = strncmp(p, "on", 2) == 0;
								break;
							case TYPE_INT:
								*keys[key].var.integer = atoi(p);
								break;
							case TYPE_FLOAT:
								*keys[key].var.floater = atof(p);
								break;
						}
						key = -1;
					} else
						key++;
				}
			}
		}
		fclose(fp);
	}
}

/*******************************************************************************\
|* LoadRCFile                                                                   |
\*******************************************************************************/
void LoadRCFile(rckeys *keys)
{
	char	home_file[NUT_PATH_MAX];
	char	*p;

#ifdef DEBUG
	printf("Loading rc files\n");
#endif

	ParseRCFile(MAINRC_FILE, keys);

	p = getenv("HOME");
	home_file[0] = '\0';
	if (p) {
#if (defined HAVE_REALPATH) && HAVE_REALPATH
		char	resolved_path[NUT_PATH_MAX];
		if (realpath(p, resolved_path)) {
			p = resolved_path;
		} else {
			fprintf(stderr, "Invalid or unsafe HOME directory: %s\n", p);
			p = NULL;
		}

		if (p)
#endif
		/* Sanity-check that HOME dir is absolute, does not have
		 * any escape chars and/or dot-dot upstaging */
		if (p[0] == '/' && (strcmp(p, "/../") || strcmp(p, "\\"))) {
			snprintf(home_file, sizeof(home_file), "%s/%s", p, RC_FILE);
			ParseRCFile(home_file, keys);
		} else {
			fprintf(stderr, "Invalid or unsafe HOME directory: %s\n", p);
		}
	}
}

/*******************************************************************************\
|* ParseCMDLine()
\*******************************************************************************/
void ParseCMDLine(int argc, char *argv[])
{
	char	*p;
	int	c;
#if 0
	char	*cmdline;
	int	i;
#endif

#ifdef HAVE_GETOPT_LONG
		static struct option long_options[] =
		{
			{"alarmint",required_argument,NULL,'A'},
			{"blinkrate",required_argument,NULL,'b'},
			{"beepvol",required_argument,NULL,'B'},
			{"criticalevel",required_argument,NULL,'C'},
			{"display",required_argument,NULL,'d'},
			{"help",no_argument,NULL,'h'},
			{"lowcolor",no_argument,NULL,'l'},
			{"lowlevel",required_argument,NULL,'L'},
			{"upsname",required_argument,NULL,'U'},
			{"version",no_argument,NULL,'v'},
			{"verbose",no_argument,NULL,'V'},
			{"windowed",no_argument,NULL,'w'},
			{0, 0, 0, 0}
		};

		#define GETOPTFUNC(x,y,z) getopt_long(x,y,"-" z, long_options, NULL)
		#define GETOPTENDCHAR -1
#else	/* ! HAVE_GETOPT_LONG */
		#define GETOPTFUNC(x,y,z) getopt(x,y,z)
		#define GETOPTENDCHAR EOF
#endif	/* ! HAVE_GETOPT_LONG */

	while(1)
	{
		if ((c=GETOPTFUNC (argc, argv, "A:b:B:C:d:hlL:U:vVw")) == GETOPTENDCHAR)
			break;

		switch (c)
		{
			case 'A':
				Alert = 1;
				printf ("option A : valeur %s\n", optarg);
				p = strchr(optarg, ',');
				if (p != NULL)
					*p = '\0';
				LAlertRate = atof(optarg);
				CAlertRate = atof(p+1);
				printf ("option A : valeur LAlertRate = %f(%s), CAlertRate = %f(%s)\n",
					LAlertRate, optarg, CAlertRate, (p+1));
				break;
			case 'b':
				BlinkRate = atof(optarg);
				break;
			case 'C':
				CriticalLevel = atoi(optarg);
				break;
			case 'L':
				LowLevel = atoi(optarg);
				break;
			case 'l':
				UseLowColorPixmap = 1;
				break;
			case 'V':
				Verbose = 1;
				break;
			case 'v':
				printf("\nThis is wmnut version: %s\n", VERSION);
#if (defined HAVE_NUT_VERSION) && (HAVE_NUT_VERSION)
				printf("Built against NUT version: %s\n", NUT_VERSION);
#endif

				printf("\nCopyright 2001-2016 Arnaud Quette\n");
				printf("Copyright 2021-2025 by the Network UPS Tools project\n");
				printf("Support at: %s\n", PACKAGE_BUGREPORT);

				printf("\nComplete documentation for WMNUT should be found on this system using\n");
				printf("`man wmnut' or `wmnut -h'.  If you have access to the Internet, point your\n");
				printf("browser at https://github.com/networkupstools/wmnut - the WMNUT Repository.\n\n");
				/* Previously http://wmnut.mgeops.org, now defunct; last cached at
				 * http://web.archive.org/web/20111003175836/http://wmnut.mgeops.org/
				 */
				exit(1);
			case 'w':
				WithDrawn = 0; /* not in default withdrawn mode, so in windowed mode */
				break;
			case 'B':
				Beep = 1;
				Volume = atoi(optarg);
				break;
			case 'U':
				AddHost(optarg);
				break;
			case 'h':
			default:
				printf("\n%s version: %s\n", PACKAGE, VERSION);
#if (defined HAVE_NUT_VERSION) && (HAVE_NUT_VERSION)
				printf("Built against NUT version: %s\n", NUT_VERSION);
#endif
				printf("\nUsage: %s [arguments]\n\n", PACKAGE_NAME);
				printf("-A <T1,T2>\tSend messages to users terminals when Low and critical.\n");
				printf("             \tT1 is seconds between messages when Low.\n");
				printf("             \tT2 is seconds between messages when Critical.\n");
				printf("-b <BlinkRate>\tBlink rate for red LED. (0 for no blinking.)\n");
				printf("-B <Volume>\tBeep at Critical Level with Volume (between -100%% to 100%%).\n");
				printf("-C <CriticaLvl>\tDefine level at which red LED turns on (CriticalLevel).\n");
				printf("-d <display>\tUse alternate display.\n");
				printf("-h\t\tDisplay this help screen.\n");
				printf("-l\t\tUse a low-color pixmap to conserve colors on 8-bit displays.\n");
				printf("-L <LowLevel>\tDefine level at which yellow LED turns on.\n");
				printf("             \tCriticalLevel takes precedence if LowLevel < CriticalLevel.\n");
				printf("-U <upsname>\tDefine upsname ([upsname@]hostname, default is localhost)\n");
				printf("-v \t\tPrint version (includes important WMNUT info).\n");
				printf("-V \t\tVerbose mode : display NUT available features and base value.\n");
				printf("-w \t\tWindowed mode (opposite to native Window Maker withdrawn mode).\n\n");
				exit (1);
		}
	}
}
