[c-host] initial agnostic option api and parser

This commit is contained in:
Geoffrey McRae 2019-05-09 22:06:58 +10:00
parent 22f9fa3938
commit e7345b9711
9 changed files with 465 additions and 101 deletions

View File

@ -1 +1 @@
a12-161-g4617829d41+1 a12-162-g22f9fa3938+1

View File

@ -74,6 +74,8 @@ CapturePointer;
typedef struct CaptureInterface typedef struct CaptureInterface
{ {
const char * (*getName )(); const char * (*getName )();
void (*initOptions )();
bool (*create )(); bool (*create )();
bool (*init )(void * pointerShape, const unsigned int pointerSize); bool (*init )(void * pointerShape, const unsigned int pointerSize);
void (*stop )(); void (*stop )();

View File

@ -22,6 +22,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include <stdbool.h> #include <stdbool.h>
int app_main(int argc, char * argv[]); int app_main(int argc, char * argv[]);
bool app_init();
void app_quit(); void app_quit();
// these must be implemented for each OS // these must be implemented for each OS

View File

@ -19,6 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/platform.h" #include "interface/platform.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/option.h"
#include <assert.h> #include <assert.h>
#include <getopt.h> #include <getopt.h>
@ -40,18 +41,8 @@ struct app
void * shmMap; void * shmMap;
}; };
struct params
{
const char * shmDevice;
};
static struct app app; static struct app app;
static struct params params =
{
.shmDevice = "uio0"
};
struct osThreadHandle struct osThreadHandle
{ {
const char * name; const char * name;
@ -71,31 +62,39 @@ int main(int argc, char * argv[])
{ {
app.executable = argv[0]; app.executable = argv[0];
static struct option longOptions[] = struct Option options[] =
{ {
{"shmDevice", required_argument, 0, 'f'}, {
{0, 0, 0, 0} .module = "os",
.name = "shmDevice",
.description = "The IVSHMEM device to use",
.value = {
.type = OPTION_TYPE_STRING,
.v.x_string = "uio0"
},
.validator = NULL,
.printHelp = NULL
},
{0}
}; };
int optionIndex = 0; option_register(options);
while(true)
{
int c = getopt_long(argc, argv, "f:", longOptions, &optionIndex);
if (c == -1)
break;
switch(c) int result = app_main(argc, argv);
{ os_shmemUnmap();
case 'f': close(app.shmFD);
params.shmDevice = optarg;
break; return result;
} }
}
bool app_init()
{
const char * shmDevice = option_get_string("os", "shmDevice");
// check the deice name // check the deice name
{ {
char file[100] = "/sys/class/uio/"; char file[100] = "/sys/class/uio/";
strncat(file, params.shmDevice, sizeof(file) - 1); strncat(file, shmDevice, sizeof(file) - 1);
strncat(file, "/name" , sizeof(file) - 1); strncat(file, "/name" , sizeof(file) - 1);
int fd = open(file, O_RDONLY); int fd = open(file, O_RDONLY);
@ -103,7 +102,7 @@ int main(int argc, char * argv[])
{ {
DEBUG_ERROR("Failed to open: %s", file); DEBUG_ERROR("Failed to open: %s", file);
DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?"); DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?");
return -1; return false;
} }
char name[32]; char name[32];
@ -112,7 +111,7 @@ int main(int argc, char * argv[])
{ {
DEBUG_ERROR("Failed to read: %s", file); DEBUG_ERROR("Failed to read: %s", file);
close(fd); close(fd);
return -1; return false;
} }
name[len] = '\0'; name[len] = '\0';
close(fd); close(fd);
@ -126,21 +125,21 @@ int main(int argc, char * argv[])
if (strcmp(name, "KVMFR") != 0) if (strcmp(name, "KVMFR") != 0)
{ {
DEBUG_ERROR("Device is not a KVMFR device \"%s\" reports as: %s", file, name); DEBUG_ERROR("Device is not a KVMFR device \"%s\" reports as: %s", file, name);
return -1; return false;
} }
} }
// get the device size // get the device size
{ {
char file[100] = "/sys/class/uio/"; char file[100] = "/sys/class/uio/";
strncat(file, params.shmDevice , sizeof(file) - 1); strncat(file, shmDevice , sizeof(file) - 1);
strncat(file, "/maps/map0/size", sizeof(file) - 1); strncat(file, "/maps/map0/size", sizeof(file) - 1);
int fd = open(file, O_RDONLY); int fd = open(file, O_RDONLY);
if (fd < 0) if (fd < 0)
{ {
DEBUG_ERROR("Failed to open: %s", file); DEBUG_ERROR("Failed to open: %s", file);
return -1; return false;
} }
char size[32]; char size[32];
@ -149,7 +148,7 @@ int main(int argc, char * argv[])
{ {
DEBUG_ERROR("Failed to read: %s", file); DEBUG_ERROR("Failed to read: %s", file);
close(fd); close(fd);
return -1; return false;
} }
size[len] = '\0'; size[len] = '\0';
close(fd); close(fd);
@ -160,13 +159,13 @@ int main(int argc, char * argv[])
// open the device // open the device
{ {
char file[100] = "/dev/"; char file[100] = "/dev/";
strncat(file, params.shmDevice, sizeof(file) - 1); strncat(file, shmDevice, sizeof(file) - 1);
app.shmFD = open(file, O_RDWR, (mode_t)0600); app.shmFD = open(file, O_RDWR, (mode_t)0600);
app.shmMap = MAP_FAILED; app.shmMap = MAP_FAILED;
if (app.shmFD < 0) if (app.shmFD < 0)
{ {
DEBUG_ERROR("Failed to open: %s", file); DEBUG_ERROR("Failed to open: %s", file);
return -1; return false;
} }
DEBUG_INFO("KVMFR Device : %s", file); DEBUG_INFO("KVMFR Device : %s", file);
@ -174,11 +173,7 @@ int main(int argc, char * argv[])
signal(SIGINT, sigHandler); signal(SIGINT, sigHandler);
int result = app_main(argc, argv); return true;
os_shmemUnmap();
close(app.shmFD);
return result;
} }
const char * os_getExecutable() const char * os_getExecutable()
@ -198,7 +193,8 @@ bool os_shmemMmap(void **ptr)
app.shmMap = mmap(0, app.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, app.shmFD, 0); app.shmMap = mmap(0, app.shmSize, PROT_READ | PROT_WRITE, MAP_SHARED, app.shmFD, 0);
if (app.shmMap == MAP_FAILED) if (app.shmMap == MAP_FAILED)
{ {
DEBUG_ERROR("Failed to map the shared memory device: %s", params.shmDevice); const char * shmDevice = option_get_string("os", "shmDevice");
DEBUG_ERROR("Failed to map the shared memory device: %s", shmDevice);
return false; return false;
} }
} }

View File

@ -110,11 +110,6 @@ static BOOL WINAPI CtrlHandler(DWORD dwCtrlType)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{ {
int result = 0;
HDEVINFO deviceInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
// convert the command line to the standard argc and argv // convert the command line to the standard argc and argv
LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc); LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &app.argc);
app.argv = malloc(sizeof(char *) * app.argc); app.argv = malloc(sizeof(char *) * app.argc);
@ -143,57 +138,6 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
// always flush stderr // always flush stderr
setbuf(stderr, NULL); setbuf(stderr, NULL);
deviceInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
memset(&deviceInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA));
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, 0, &deviceInterfaceData) == FALSE)
{
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_ITEMS)
{
DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error);
result = -1;
goto finish;
}
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
result = -1;
goto finish;
}
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &reqSize, NULL);
if (!reqSize)
{
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
result = -1;
goto finish;
}
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1);
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, infData, reqSize, NULL, NULL))
{
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
result = -1;
goto finish;
}
app.shmemHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (app.shmemHandle == INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
free(infData);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
result = -1;
goto finish;
}
free(infData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
// setup a handler for ctrl+c // setup a handler for ctrl+c
SetConsoleCtrlHandler(CtrlHandler, TRUE); SetConsoleCtrlHandler(CtrlHandler, TRUE);
@ -260,6 +204,62 @@ finish:
return result; return result;
} }
bool app_init()
{
int result = 0;
HDEVINFO deviceInfoSet;
PSP_DEVICE_INTERFACE_DETAIL_DATA infData = NULL;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
deviceInfoSet = SetupDiGetClassDevs(NULL, NULL, NULL, DIGCF_PRESENT | DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE);
memset(&deviceInterfaceData, 0, sizeof(SP_DEVICE_INTERFACE_DATA));
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (SetupDiEnumDeviceInterfaces(deviceInfoSet, NULL, &GUID_DEVINTERFACE_IVSHMEM, 0, &deviceInterfaceData) == FALSE)
{
DWORD error = GetLastError();
if (error == ERROR_NO_MORE_ITEMS)
{
DEBUG_WINERROR("Unable to enumerate the device, is it attached?", error);
return false;
}
DEBUG_WINERROR("SetupDiEnumDeviceInterfaces failed", error);
return false;
}
DWORD reqSize = 0;
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, NULL, 0, &reqSize, NULL);
if (!reqSize)
{
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
infData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)calloc(reqSize, 1);
infData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, infData, reqSize, NULL, NULL))
{
free(infData);
DEBUG_WINERROR("SetupDiGetDeviceInterfaceDetail", GetLastError());
return false;
}
app.shmemHandle = CreateFile(infData->DevicePath, 0, 0, NULL, OPEN_EXISTING, 0, 0);
if (app.shmemHandle == INVALID_HANDLE_VALUE)
{
SetupDiDestroyDeviceInfoList(deviceInfoSet);
free(infData);
DEBUG_WINERROR("CreateFile returned INVALID_HANDLE_VALUE", GetLastError());
return false;
}
free(infData);
SetupDiDestroyDeviceInfoList(deviceInfoSet);
return true;
}
const char * os_getExecutable() const char * os_getExecutable()
{ {
return app.executable; return app.executable;

View File

@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA
#include "interface/capture.h" #include "interface/capture.h"
#include "dynamic/capture.h" #include "dynamic/capture.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/option.h"
#include "common/locking.h" #include "common/locking.h"
#include "common/KVMFR.h" #include "common/KVMFR.h"
#include "common/crash.h" #include "common/crash.h"
@ -256,11 +257,29 @@ static bool captureRestart()
return true; return true;
} }
// this is called from the platform specific startup routine
int app_main(int argc, char * argv[]) int app_main(int argc, char * argv[])
{ {
if (!installCrashHandler(os_getExecutable())) if (!installCrashHandler(os_getExecutable()))
DEBUG_WARN("Failed to install the crash handler"); DEBUG_WARN("Failed to install the crash handler");
// register capture interface options
for(int i = 0; CaptureInterfaces[i]; ++i)
if (CaptureInterfaces[i]->initOptions)
CaptureInterfaces[i]->initOptions();
// parse the command line arguments
if (!option_parse(argc, argv))
{
option_free();
DEBUG_ERROR("Failure to parse the command line");
return -1;
}
// perform platform specific initialization
if (!app_init())
return -1;
unsigned int shmemSize = os_shmemSize(); unsigned int shmemSize = os_shmemSize();
uint8_t * shmemMap = NULL; uint8_t * shmemMap = NULL;
int exitcode = 0; int exitcode = 0;

View File

@ -5,10 +5,14 @@ include_directories(
${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/include
) )
set(SOURCES
src/option.c
)
if(WIN32) if(WIN32)
add_library(lg_common STATIC src/crash.windows.c) add_library(lg_common STATIC src/crash.windows.c ${SOURCES})
else() else()
add_library(lg_common STATIC src/crash.linux.c) add_library(lg_common STATIC src/crash.linux.c ${SOURCES})
target_link_libraries(lg_common bfd) target_link_libraries(lg_common bfd)
endif() endif()

View File

@ -0,0 +1,75 @@
/*
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 <stdint.h>
#include <stdbool.h>
enum OptionType
{
OPTION_TYPE_NONE = 0,
OPTION_TYPE_INT,
OPTION_TYPE_STRING,
OPTION_TYPE_BOOL
};
struct OptionState;
struct OptionValue
{
enum OptionType type;
union
{
int x_int;
char * x_string;
bool x_bool;
}
v;
// internal state
struct OptionState * state;
};
struct Option
{
const char * module;
const char * name;
const char * description;
struct OptionValue value;
bool (*validator)(struct OptionValue * value);
void (*printHelp)();
};
// register an NULL terminated array of options
bool option_register(struct Option options[]);
// lookup the value of an option
struct OptionValue * option_get (const char * module, const char * name);
int option_get_int (const char * module, const char * name);
const char * option_get_string(const char * module, const char * name);
bool option_get_bool (const char * module, const char * name);
// called by the main application to parse the command line arguments
bool option_parse(int argc, char * argv[]);
// print out the options, help, and their current values
void option_print();
// final cleanup
void option_free();

267
common/src/option.c Normal file
View File

@ -0,0 +1,267 @@
/*
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 <string.h>
#include <assert.h>
struct OptionGroup
{
const char * module;
struct Option ** options;
int count;
int pad;
};
struct State
{
struct Option * options;
int oCount;
struct OptionGroup * groups;
int gCount;
};
struct State state =
{
.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;
}
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)
{
option_print();
return false;
}
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;
}
switch(o->value.type)
{
case OPTION_TYPE_INT:
o->value.v.x_int = atol(value);
break;
case OPTION_TYPE_STRING:
free(o->value.v.x_string);
o->value.v.x_string = strdup(value);
break;
case OPTION_TYPE_BOOL:
o->value.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");
assert(false);
break;
}
if (o->validator)
if (!o->validator(&o->value))
{
DEBUG_ERROR("Invalid value provided to option: %s", argv[a]);
if (o->printHelp)
o->printHelp();
return false;
}
}
return true;
}
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\n", o->module, state.groups[g].pad, o->name, o->description);
}
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];
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;
}