LookingGlass/common/src/option.c

495 lines
10 KiB
C
Raw Normal View History

/*
KVMGFX Client - A KVM Client for VGA Passthrough
Copyright (C) 2017-2019 Geoffrey McRae <geoff@hostfission.com>
https://looking-glass.hostfission.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 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
*/
#include "common/option.h"
#include "common/debug.h"
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
struct OptionGroup
{
const char * module;
struct Option ** options;
int count;
int pad;
};
struct State
{
2019-05-11 01:50:26 +00:00
bool doHelp;
struct Option * options;
int oCount;
struct OptionGroup * groups;
int gCount;
};
2019-05-11 09:07:10 +00:00
static struct State state =
{
2019-05-11 01:50:26 +00:00
.doHelp = false,
.options = NULL,
.oCount = 0,
.groups = NULL,
.gCount = 0
};
bool option_register(struct Option options[])
{
int new = 0;
for(int i = 0; options[i].value.type != OPTION_TYPE_NONE; ++i)
++new;
state.options = realloc(
state.options,
sizeof(struct Option) * (state.oCount + new)
);
for(int i = 0; options[i].value.type != OPTION_TYPE_NONE; ++i)
{
struct Option * o = &state.options[state.oCount + i];
memcpy(o, &options[i], sizeof(struct Option));
// ensure the string is locally allocated
if (o->value.type == OPTION_TYPE_STRING)
o->value.v.x_string = strdup(o->value.v.x_string);
// add the option to the correct group for help printout
bool found = false;
for(int g = 0; g < state.gCount; ++g)
{
struct OptionGroup * group = &state.groups[g];
if (strcmp(group->module, o->module) != 0)
continue;
found = true;
group->options = realloc(
group->options,
sizeof(struct Option *) * (group->count + 1)
);
group->options[group->count] = o;
int len = strlen(o->name);
if (len > group->pad)
group->pad = len;
++group->count;
}
if (!found)
{
state.groups = realloc(
state.groups,
sizeof(struct OptionGroup) * (state.gCount + 1)
);
struct OptionGroup * group = &state.groups[state.gCount];
++state.gCount;
group->module = o->module;
group->options = malloc(sizeof(struct Option *));
group->options[0] = o;
group->count = 1;
group->pad = strlen(o->name);
}
}
state.oCount += new;
return true;
};
void option_free()
{
for(int i = 0; i < state.oCount; ++i)
{
struct Option * o = &state.options[i];
if (o->value.type == OPTION_TYPE_STRING)
free(o->value.v.x_string);
}
free(state.options);
state.options = NULL;
state.oCount = 0;
free(state.groups);
state.groups = NULL;
state.gCount = 0;
}
static bool option_set(struct OptionValue * v, const char * value)
{
switch(v->type)
{
case OPTION_TYPE_INT:
v->v.x_int = atol(value);
break;
case OPTION_TYPE_STRING:
free(v->v.x_string);
v->v.x_string = strdup(value);
break;
case OPTION_TYPE_BOOL:
v->v.x_bool =
strcmp(value, "1" ) == 0 ||
strcmp(value, "yes" ) == 0 ||
strcmp(value, "true") == 0 ||
strcmp(value, "on" ) == 0;
break;
default:
DEBUG_ERROR("BUG: Invalid option type, this should never happen");
return false;
}
return true;
}
bool option_parse(int argc, char * argv[])
{
for(int a = 1; a < argc; ++a)
{
if (strcmp(argv[a], "-h") == 0 || strcmp(argv[a], "--help") == 0)
{
2019-05-11 01:50:26 +00:00
state.doHelp = true;
continue;
}
char * arg = strdup(argv[a]);
char * module = strtok(arg , ":");
char * name = strtok(NULL, "=");
char * value = strtok(NULL, "" );
if (!module || !name || !value)
{
DEBUG_WARN("Ignored invalid argument: %s", argv[a]);
free(arg);
continue;
}
bool found = false;
struct Option * o;
for(int i = 0; i < state.oCount; ++i)
{
o = &state.options[i];
if ((strcmp(o->module, module) != 0) || (strcmp(o->name, name) != 0))
continue;
found = true;
break;
}
if (!found)
{
DEBUG_WARN("Ignored unknown argument: %s", argv[a]);
free(arg);
continue;
}
if (!option_set(&o->value, value))
{
DEBUG_ERROR("Failed to set the option value");
free(arg);
continue;
}
free(arg);
}
return true;
}
static char * file_parse_module(FILE * fp)
{
char * module = NULL;
int len = 0;
for(int c = fgetc(fp); !feof(fp); c = fgetc(fp))
{
switch(c)
{
case ']':
if (module)
module[len] = '\0';
return module;
case '\r':
case '\n':
free(module);
return NULL;
default:
if (len % 32 == 0)
module = realloc(module, len + 32 + 1);
module[len++] = c;
}
}
if (module)
free(module);
return NULL;
}
bool option_load(const char * filename)
{
FILE * fp = fopen(filename, "r");
if (!fp)
return false;
bool result = true;
int lineno = 1;
char * module = NULL;
bool line = true;
bool expectLine = false;
bool expectValue = false;
char * name = NULL;
int nameLen = 0;
char * value = NULL;
int valueLen = 0;
char ** p = &name;
int * len = &nameLen;
for(int c = fgetc(fp); !feof(fp); c = fgetc(fp))
{
switch(c)
{
case '[':
if (expectLine)
{
DEBUG_ERROR("Syntax error on line %d, expected new line", lineno);
result = false;
goto exit;
}
if (line)
{
free(module);
module = file_parse_module(fp);
if (!module)
{
DEBUG_ERROR("Syntax error on line %d, failed to parse the module", lineno);
result = false;
goto exit;
}
line = false;
expectLine = true;
continue;
}
if (*len % 32 == 0)
*p = realloc(*p, *len + 32 + 1);
(*p)[(*len)++] = c;
break;
case '\r':
continue;
case '\n':
if (name)
{
struct OptionValue * o = option_get(module, name);
if (!o)
DEBUG_WARN("Ignored unknown option %s:%s", module, name);
else
{
if (!option_set(o, value))
DEBUG_ERROR("Failed to set the option value");
}
}
line = true;
expectLine = false;
expectValue = false;
++lineno;
p = &name;
len = &nameLen;
free(name);
name = NULL;
nameLen = 0;
free(value);
value = NULL;
valueLen = 0;
break;
case '=':
if (!expectValue)
{
if (!name)
{
DEBUG_ERROR("Syntax error on line %d, expected option name", lineno);
result = false;
goto exit;
}
name[nameLen] = '\0';
expectValue = true;
p = &value;
len = &valueLen;
continue;
}
if (*len % 32 == 0)
*p = realloc(*p, *len + 32 + 1);
(*p)[(*len)++] = c;
break;
default:
if (expectLine)
{
DEBUG_ERROR("Syntax error on line %d, expected new line", lineno);
result = false;
goto exit;
}
line = false;
if (*len % 32 == 0)
*p = realloc(*p, *len + 32 + 1);
(*p)[(*len)++] = c;
break;
}
}
2019-05-09 13:05:33 +00:00
exit:
fclose(fp);
free(module);
free(name );
free(value );
return result;
}
bool option_validate()
{
2019-05-11 01:50:26 +00:00
if (state.doHelp)
{
option_print();
return false;
}
// validate the option values
bool ok = true;
for(int i = 0; i < state.oCount; ++i)
{
struct Option * o = &state.options[i];
const char * error = NULL;
if (o->validator)
if (!o->validator(&o->value, &error))
{
printf("\nInvalid value provided to the option: %s:%s\n", o->module, o->name);
if (error)
printf("\n Error: %s\n", error);
if (o->printHelp)
{
printf("\n");
o->printHelp();
}
ok = false;
}
}
if (!ok)
printf("\n");
return ok;
}
void option_print()
{
printf(
"The following is a complete list of options accepted by this application\n\n"
);
for(int g = 0; g < state.gCount; ++g)
{
for(int i = 0; i < state.groups[g].count; ++i)
{
struct Option * o = state.groups[g].options[i];
printf(" %s:%-*s - %s [", o->module, state.groups[g].pad, o->name, o->description);
2019-05-11 01:50:26 +00:00
switch(o->value.type)
{
case OPTION_TYPE_INT:
printf("%d]\n", o->value.v.x_int);
break;
case OPTION_TYPE_STRING:
printf("%s]\n", o->value.v.x_string);
break;
case OPTION_TYPE_BOOL:
printf("%s]\n", o->value.v.x_bool ? "yes" : "no");
break;
default:
DEBUG_ERROR("BUG: Invalid option type, this should never happen");
assert(false);
break;
}
}
printf("\n");
}
}
struct OptionValue * option_get(const char * module, const char * name)
{
for(int i = 0; i < state.oCount; ++i)
{
struct Option * o = &state.options[i];
2019-05-09 12:47:48 +00:00
if ((strcmp(o->module, module) == 0) && (strcmp(o->name, name) == 0))
return &o->value;
}
return NULL;
}
int option_get_int(const char * module, const char * name)
{
struct OptionValue * o = option_get(module, name);
if (!o)
return -1;
assert(o->type == OPTION_TYPE_INT);
return o->v.x_int;
}
const char * option_get_string(const char * module, const char * name)
{
struct OptionValue * o = option_get(module, name);
if (!o)
return NULL;
assert(o->type == OPTION_TYPE_STRING);
return o->v.x_string;
}
bool option_get_bool(const char * module, const char * name)
{
struct OptionValue * o = option_get(module, name);
if (!o)
return false;
assert(o->type == OPTION_TYPE_BOOL);
return o->v.x_bool;
}