diff --git a/VERSION b/VERSION index 8bd7acff..b9bb979e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -a12-161-g4617829d41+1 \ No newline at end of file +a12-162-g22f9fa3938+1 \ No newline at end of file diff --git a/c-host/include/interface/capture.h b/c-host/include/interface/capture.h index 9a48892e..535c04f9 100644 --- a/c-host/include/interface/capture.h +++ b/c-host/include/interface/capture.h @@ -74,6 +74,8 @@ CapturePointer; typedef struct CaptureInterface { const char * (*getName )(); + void (*initOptions )(); + bool (*create )(); bool (*init )(void * pointerShape, const unsigned int pointerSize); void (*stop )(); diff --git a/c-host/include/interface/platform.h b/c-host/include/interface/platform.h index 1cedbd74..eede1bb8 100644 --- a/c-host/include/interface/platform.h +++ b/c-host/include/interface/platform.h @@ -22,6 +22,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include int app_main(int argc, char * argv[]); +bool app_init(); void app_quit(); // these must be implemented for each OS diff --git a/c-host/platform/Linux/src/platform.c b/c-host/platform/Linux/src/platform.c index a7502e7c..0ed3a521 100644 --- a/c-host/platform/Linux/src/platform.c +++ b/c-host/platform/Linux/src/platform.c @@ -19,6 +19,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include "interface/platform.h" #include "common/debug.h" +#include "common/option.h" #include #include @@ -40,18 +41,8 @@ struct app void * shmMap; }; -struct params -{ - const char * shmDevice; -}; - static struct app app; -static struct params params = -{ - .shmDevice = "uio0" -}; - struct osThreadHandle { const char * name; @@ -71,39 +62,47 @@ int main(int argc, char * argv[]) { 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; - while(true) - { - int c = getopt_long(argc, argv, "f:", longOptions, &optionIndex); - if (c == -1) - break; + option_register(options); - switch(c) - { - case 'f': - params.shmDevice = optarg; - break; - } - } + int result = app_main(argc, argv); + os_shmemUnmap(); + close(app.shmFD); + + return result; +} + +bool app_init() +{ + const char * shmDevice = option_get_string("os", "shmDevice"); // check the deice name { char file[100] = "/sys/class/uio/"; - strncat(file, params.shmDevice, sizeof(file) - 1); - strncat(file, "/name" , sizeof(file) - 1); + strncat(file, shmDevice, sizeof(file) - 1); + strncat(file, "/name" , sizeof(file) - 1); int fd = open(file, O_RDONLY); if (fd < 0) { DEBUG_ERROR("Failed to open: %s", file); DEBUG_ERROR("Did you remmeber to modprobe the kvmfr module?"); - return -1; + return false; } char name[32]; @@ -112,7 +111,7 @@ int main(int argc, char * argv[]) { DEBUG_ERROR("Failed to read: %s", file); close(fd); - return -1; + return false; } name[len] = '\0'; close(fd); @@ -126,21 +125,21 @@ int main(int argc, char * argv[]) if (strcmp(name, "KVMFR") != 0) { DEBUG_ERROR("Device is not a KVMFR device \"%s\" reports as: %s", file, name); - return -1; + return false; } } // get the device size { 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); int fd = open(file, O_RDONLY); if (fd < 0) { DEBUG_ERROR("Failed to open: %s", file); - return -1; + return false; } char size[32]; @@ -149,7 +148,7 @@ int main(int argc, char * argv[]) { DEBUG_ERROR("Failed to read: %s", file); close(fd); - return -1; + return false; } size[len] = '\0'; close(fd); @@ -160,13 +159,13 @@ int main(int argc, char * argv[]) // open the device { 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.shmMap = MAP_FAILED; if (app.shmFD < 0) { DEBUG_ERROR("Failed to open: %s", file); - return -1; + return false; } DEBUG_INFO("KVMFR Device : %s", file); @@ -174,11 +173,7 @@ int main(int argc, char * argv[]) signal(SIGINT, sigHandler); - int result = app_main(argc, argv); - os_shmemUnmap(); - close(app.shmFD); - - return result; + return true; } 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); 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; } } diff --git a/c-host/platform/Windows/src/platform.c b/c-host/platform/Windows/src/platform.c index dd5f7fd5..7d91d610 100644 --- a/c-host/platform/Windows/src/platform.c +++ b/c-host/platform/Windows/src/platform.c @@ -110,11 +110,6 @@ static BOOL WINAPI CtrlHandler(DWORD dwCtrlType) 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 LPWSTR * wargv = CommandLineToArgvW(GetCommandLineW(), &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 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 SetConsoleCtrlHandler(CtrlHandler, TRUE); @@ -260,6 +204,62 @@ finish: 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() { return app.executable; diff --git a/c-host/src/app.c b/c-host/src/app.c index 702be008..f0a6586b 100644 --- a/c-host/src/app.c +++ b/c-host/src/app.c @@ -21,6 +21,7 @@ Place, Suite 330, Boston, MA 02111-1307 USA #include "interface/capture.h" #include "dynamic/capture.h" #include "common/debug.h" +#include "common/option.h" #include "common/locking.h" #include "common/KVMFR.h" #include "common/crash.h" @@ -256,11 +257,29 @@ static bool captureRestart() return true; } +// this is called from the platform specific startup routine int app_main(int argc, char * argv[]) { if (!installCrashHandler(os_getExecutable())) 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(); uint8_t * shmemMap = NULL; int exitcode = 0; diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 47b61309..178c7929 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -5,10 +5,14 @@ include_directories( ${PROJECT_SOURCE_DIR}/include ) +set(SOURCES + src/option.c +) + if(WIN32) - add_library(lg_common STATIC src/crash.windows.c) + add_library(lg_common STATIC src/crash.windows.c ${SOURCES}) 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) endif() diff --git a/common/include/common/option.h b/common/include/common/option.h new file mode 100644 index 00000000..3ee10e51 --- /dev/null +++ b/common/include/common/option.h @@ -0,0 +1,75 @@ +/* +KVMGFX Client - A KVM Client for VGA Passthrough +Copyright (C) 2017-2019 Geoffrey McRae +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 +#include + +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(); \ No newline at end of file diff --git a/common/src/option.c b/common/src/option.c new file mode 100644 index 00000000..ee4c40f4 --- /dev/null +++ b/common/src/option.c @@ -0,0 +1,267 @@ +/* +KVMGFX Client - A KVM Client for VGA Passthrough +Copyright (C) 2017-2019 Geoffrey McRae +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 +#include +#include +#include + +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; +} \ No newline at end of file