aboutsummaryrefslogblamecommitdiffstats
path: root/src/settings.c
blob: 0333a41c06489e7e108d1737d03458eba4580ea1 (plain) (tree)





















































































































































































































































                                                                                                              











































































                                                                                                   
/**
 * Copyright © 2016  Mattias Andrée <maandree@member.fsf.org>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#include "settings.h"
#include "arg.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <limits.h>



/**
 * The name of the process.
 */
char *argv0 = NULL;



/**
 * Print usage information and exit if a condition is met.
 * 
 * @param  condition  Do no do anything iff this is zero.
 */
static void
usage(int condition)
{
	if (condition) {
		fprintf(stderr,
		        "Usage: %s [OPTIONS]...\n"
	        	"See `man 1 radharc` for more information.\n",
		        !argv0 ? "radharc" : argv0);
		exit(2);
	}
}


/**
 * Parse a temperature string.
 * 
 * @param   str        The temperature string.
 * @param   temp       Output parameter for the temperature.
 *                     Overflow is truncated.
 * @param   direction  Unless `NULL`, will be set to +1 if `str`
 *                     starts with a '+', `-1` if `str` starts
 *                     with a '-', 0 otherwise. If `NULL` and
 *                     `str` starts with a '-' or a '+', the
 *                     function will fail.
 * @param   lower      The function shall fail if the evaluated
 *                     temperature is below this.
 * @return             0 on success, -1 on error.
 */
static int
parse_temperature(const char *str, long int *temp, int *direction, int lower)
{
	int dir = 0;
	char *end;
	switch (*str) {
	case '-':  dir = -1;  break;
	case '+':  dir = +1;  break;
	}
	str += !!dir;
	if (dir && !direction)  return -1;
	else if (dir)           *direction = dir;
	if (!isdigit(*str))     return -1;
	*temp = (errno = 0, strtol)(str, &end, 10);
	if ((errno && !((errno == ERANGE) && (*temp == LONG_MAX))) || *end || (*temp < lower))
		return -1;
	return 0;
}


/**
 * Parse a string as a positively valued `struct timespec`.
 * 
 * @param   str  The string.
 * @param   ts   Output parameter for the value.
 * @return       0 on success, -1 on error. 
 */
static int
parse_timespec(const char *str, struct timespec *ts)
{
	int points = 0;
	memset(ts, 0, sizeof(*ts));

	/* Parse seconds. */
	if (!isdigit(*str))
		return -1;
	while (isdigit(*str)) {
		ts->tv_sec *= 10;
		ts->tv_sec += *str++ & 15;
	}

	/* End? */
	if (!*str)        return 0;
	if (*str != '.')  return -1;

	/* Parse nanoseconds.*/
	for (; (points++ < 9) && isdigit(*str); str++) {
		ts->tv_nsec *= 10;
		ts->tv_nsec += *str++ & 15;
	}
	if (points == 9) {
		if (!isdigit(*str))  return -1;
		if (*str++ >= '5') {
			ts->tv_nsec += 1;
			if (ts->tv_nsec == 1000000000L)
				ts->tv_sec += 1, ts->tv_nsec = 0;
		}
	}
	while (isdigit(*str))  str++;
	if (*str)  return -1;

	/* End! */
	return 0;
}


/**
 * Parse a latitude or a longitude value.
 * 
 * @param   str    The string.
 * @param   loc    Output parameter for the value.
 * @param   limit  The limit of the absolute value.
 * @return         0 on success, -1 on error.
 */
static int
parse_location(char *str, double *loc, double limit)
{
	char* end;
	if (strstr(str, "−") == str) /* Support proper minus. */
		*(str += strlen("−") - 1) = '-';
	if ((*str != '-') && (*str != '+') && (*str != '.') && !isdigit(*str))
		return -1;
	*loc = (errno = 0, strtod)(str, &end);
	return -(errno || *loc || (fabs(*loc) > limit));
}


/**
 * Parse the command line.
 * 
 * @param  argc      The number of elements in `argv`.
 * @param  argv      The commnad line arguments including the zeroth elemenet.
 * @param  settings  Output parameter for the settings.
 */
void
parse_command_line(int argc, char *argv[], struct settings *settings)
{
	int location_set = 0;
	char *p;
	char *arg;
	int c = 0;

	memset(settings, 0, sizeof(*settings));
	settings->natural_temp = 6500;
	settings->day_temp     = 5500;
	settings->night_temp   = 3500;
	settings->trans_speed  = 50;

	ARGBEGIN {
	case 'l':
		usage(!(p = strchr(arg = ARGF(), ':')));
		*p++ = '\0', location_set = 1;
		usage(parse_location(arg, &(settings->latitude),  90.0));
		usage(parse_location(arg, &(settings->longitude), 180.0));
		break;
	case 't':
		settings->temp = settings->day_temp = settings->night_temp = 0;
		settings->temp_direction = 0;
		if ((p = strchr(arg = ARGF(), ':'))) {
			*p++ = '\0';
			usage(parse_temperature(arg, &(settings->day_temp), NULL, 1000));
			usage(parse_temperature(p, &(settings->night_temp), NULL, 1000));
		} else {
			usage(parse_temperature(arg, &(settings->temp), &(settings->temp_direction), 1000));
		}
		break;
	case 'T':
		usage(parse_temperature(ARGF(), &(settings->natural_temp), NULL, 1000));
		break;
	case 's':
		settings->trans_speed = 0;
		usage(parse_timespec(ARGF(), &(settings->transition)));
		break;
	case 'S':
		usage(parse_temperature(ARGF(), &(settings->trans_speed), NULL, 1));
		break;
	case 'h':
		usage(!(settings->hookpath = ARGF()));
		break;
	case 'd': c++; /* Fall though. */
	case 'e': c++; /* Fall though. */
	case 'm': c++;
#define REALLOC(VAR, N)  !(VAR = realloc(VAR, (N) * sizeof(*VAR)))
		settings->monitors_n++;
		if (REALLOC(settings->monitors_id,  settings->monitors_n))  goto fail;
		if (REALLOC(settings->monitors_arg, settings->monitors_n))  goto fail;
		settings->monitors_id[settings->monitors_n - 1] = ARGF();
		settings->monitors_arg[settings->monitors_n - 1] = (c == 3 ? 'm' : c == 2 ? 'e' : 'd'), c = 0;
		break;
	case 'p':  settings->print_status = 1;  break;
	case 'n':  settings->panic_start  = 1;  break;
	case 'N':  settings->panic_else   = 1;  break;
	case 'o':  settings->set_and_exit = 1;  break;
	case 'x':  settings->ignore_calib = 1;  break;
	case 'i':  settings->negative     = 1;  break;
	case 'b':  settings->use_bus      = 1;  break;
	default:   usage(1);                    break;
	} ARGEND;
	usage(argc);

	if (!location_set && !(settings->temp)) {
		fprintf(stderr,
			"%s: The -l option is mandatory, unless single value -t is used."
	        	"See `man 1 radharc` for more information.\n",
		        !argv0 ? "radharc" : argv0);
		exit(2);
	}

fail:
	perror(argv0 ? argv0 : "radharc");
	exit(1);
}


/**
 * Marshal settings into a buffer.
 * 
 * @param   buffer    The buffer, `NULL` if you want to know the required size.
 * @param   settings  The settings to marshal.
 * @return            The size of the output.
 */
size_t
marshal_settings(char *buffer, const struct settings *settings)
{
#define MARSHAL(N, DATUMP)  (n += aux = (N), (buf ? (memcpy(buf, DATUMP, aux), buf += aux, 0) : 0))

	size_t n = 0, i = 0;
	size_t aux;
	char *buf = buffer;

	if (buffer)  i = marshal_settings(NULL, settings);
	MARSHAL(sizeof(i), &i);

	MARSHAL(sizeof(*settings), settings);
	if (settings->hookpath)
		MARSHAL(strlen(settings->hookpath) + 1, settings->hookpath);
	if (settings->monitors_arg)
		MARSHAL(settings->monitors_n, settings->monitors_arg);
	for (i = 0; i < settings->monitors_n; i++)
		MARSHAL(strlen(settings->monitors_id[i]) + 1, settings->monitors_id[i]);

	return n;
}


/**
 * Unmarshal settings from a buffer.
 * 
 * @param   buffer    The buffer.
 * @param   settings  Output parameter for the settings, will be allocated.
 * @return            The number of unmarshalled bytes, 0 on error.
 */
size_t
unmarshal_settings(char *buffer, struct settings **settings)
{
#define UNMARSHAL(N, DATUMP)  (aux = (N), memcpy((DATUMP), buf, aux), buf += aux)

	size_t n, aux, i;
	struct settings *s = NULL;
	struct settings s_;
	char *buf = buffer;
	int saved_errno;

	UNMARSHAL(sizeof(n), &n);
	if (!(s = *settings = malloc(n - sizeof(n))))  return 0;
	s->monitors_id = NULL, s->monitors_arg = NULL;

	UNMARSHAL(sizeof(s_), &s_);
	if (s_.monitors_n) {
		if (!(*s = s_, s->monitors_id = malloc(s_.monitors_n * sizeof(char*))))  goto fail;
		if (!(*s = s_, s->monitors_arg = malloc(s_.monitors_n * sizeof(char))))  goto fail;
	}

	s->hookpath = buf;
	buf = strchr(buf, '\0') + 1;

	UNMARSHAL(s_.monitors_n, s->monitors_arg);
	for (i = 0; i < s_.monitors_n; i++)
		UNMARSHAL(strlen(buf + 1), s->monitors_id[i]);

	return n;

fail:
	saved_errno = errno;
	free(s->monitors_id), free(s->monitors_arg), free(s), *settings = NULL;
	errno = saved_errno;
	return 0;
}