// Copyright(c) 2018-2020, Intel Corporation // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of Intel Corporation nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #include #include #include "config_file.h" #include "monitored_device.h" #include "api/sysfs.h" #include #ifdef LOG #undef LOG #endif #define LOG(format, ...) \ log_printf("cfg: " format, ##__VA_ARGS__) #define CFG_TRY_FILE(__f) \ do { \ canon = canonicalize_file_name(__f); \ if (canon) { \ \ if (!cmd_path_is_symlink(__f)) { \ size_t len = strnlen(canon, \ sizeof(c->cfgfile) - 1); \ memcpy(c->cfgfile, \ canon, \ len); \ c->cfgfile[len] = '\0'; \ free(canon); \ return 0; \ } \ \ free(canon); \ } \ } while (0) int cfg_find_config_file(struct fpgad_config *c) { char path[PATH_MAX]; char *e; char *canon = NULL; uid_t uid; size_t len; uid = geteuid(); e = getenv("FPGAD_CONFIG_FILE"); if (e) { // try $FPGAD_CONFIG_FILE len = strnlen(e, sizeof(path) - 1); memcpy(path, e, len); path[len] = '\0'; CFG_TRY_FILE(path); } if (!uid) { CFG_TRY_FILE("/var/lib/opae/fpgad.cfg"); } else { struct passwd *passwd; passwd = getpwuid(uid); // try $HOME/.opae/fpgad.cfg snprintf(path, sizeof(path), "%s/.opae/fpgad.cfg", passwd->pw_dir); CFG_TRY_FILE(path); } CFG_TRY_FILE("/etc/opae/fpgad.cfg"); return 1; // not found } STATIC char *cfg_read_file(const char *file) { FILE *fp; size_t len; char *buf; fp = fopen(file, "r"); if (!fp) { LOG("fopen failed.\n"); return NULL; } if (fseek(fp, 0, SEEK_END)) { LOG("fseek failed.\n"); fclose(fp); return NULL; } len = (size_t)ftell(fp); ++len; // for \0 if (len == 1) { LOG("%s is empty.\n", file); fclose(fp); return NULL; } if (fseek(fp, 0, SEEK_SET)) { LOG("fseek failed.\n"); fclose(fp); return NULL; } buf = (char *)malloc(len); if (!buf) { LOG("malloc failed.\n"); fclose(fp); return NULL; } if ((fread(buf, 1, len - 1, fp) != len - 1) || ferror(fp)) { LOG("fread failed.\n"); fclose(fp); free(buf); return NULL; } fclose(fp); buf[len - 1] = '\0'; return buf; } typedef struct _cfg_vendor_device_id { uint16_t vendor_id; uint16_t device_id; struct _cfg_vendor_device_id *next; } cfg_vendor_device_id; typedef struct _cfg_plugin_configuration { char *configuration; bool enabled; char *library; cfg_vendor_device_id *devices; struct _cfg_plugin_configuration *next; } cfg_plugin_configuration; STATIC cfg_vendor_device_id *alloc_device(uint16_t vendor_id, uint16_t device_id) { cfg_vendor_device_id *p; p = (cfg_vendor_device_id *)malloc(sizeof(cfg_vendor_device_id)); if (p) { p->vendor_id = vendor_id; p->device_id = device_id; p->next = NULL; } return p; } STATIC cfg_plugin_configuration *alloc_configuration(char *configuration, bool enabled, char *library, cfg_vendor_device_id *devs) { cfg_plugin_configuration *p; p = (cfg_plugin_configuration *) malloc(sizeof(cfg_plugin_configuration)); if (p) { p->configuration = configuration; p->enabled = enabled; p->library = library; p->devices = devs; p->next = NULL; } return p; } STATIC cfg_vendor_device_id * cfg_process_plugin_devices(const char *name, json_object *j_devices) { int i; int devs; cfg_vendor_device_id *head = NULL; cfg_vendor_device_id *id = NULL; uint16_t vendor_id; uint16_t device_id; const char *s; char *endptr; if (!json_object_is_type(j_devices, json_type_array)) { LOG("'devices' JSON object not array.\n"); return NULL; } devs = json_object_array_length(j_devices); for (i = 0 ; i < devs ; ++i) { json_object *j_dev = json_object_array_get_idx(j_devices, i); json_object *j_vid; json_object *j_did; if (!json_object_is_type(j_dev, json_type_array)) { LOG("%s 'devices' entry %d not array.\n", name, i); goto out_free; } if (json_object_array_length(j_dev) != 2) { LOG("%s 'devices' entry %d not array[2].\n", name, i); goto out_free; } j_vid = json_object_array_get_idx(j_dev, 0); if (json_object_is_type(j_vid, json_type_string)) { s = json_object_get_string(j_vid); endptr = NULL; vendor_id = (uint16_t)strtoul(s, &endptr, 0); if (*endptr != '\0') { LOG("%s malformed Vendor ID at devices[%d]\n", name, i); goto out_free; } } else if (json_object_is_type(j_vid, json_type_int)) { vendor_id = (uint16_t)json_object_get_int(j_vid); } else { LOG("%s invalid Vendor ID at devices[%d]\n", name, i); goto out_free; } j_did = json_object_array_get_idx(j_dev, 1); if (json_object_is_type(j_did, json_type_string)) { s = json_object_get_string(j_did); endptr = NULL; device_id = (uint16_t)strtoul(s, &endptr, 0); if (*endptr != '\0') { LOG("%s malformed Device ID at devices[%d]\n", name, i); goto out_free; } } else if (json_object_is_type(j_did, json_type_int)) { device_id = (uint16_t)json_object_get_int(j_did); } else { LOG("%s invalid Device ID at devices[%d]\n", name, i); goto out_free; } if (!head) { head = alloc_device(vendor_id, device_id); if (!head) { LOG("malloc failed.\n"); goto out_free; } id = head; } else { id->next = alloc_device(vendor_id, device_id); if (!id->next) { LOG("malloc failed.\n"); goto out_free; } id = id->next; } } return head; out_free: for (id = head ; id ; ) { cfg_vendor_device_id *trash = id; id = id->next; free(trash); } return NULL; } STATIC int cfg_process_plugin(const char *name, json_object *j_configurations, cfg_plugin_configuration **list) { json_object *j_cfg_plugin = NULL; json_object *j_cfg_plugin_configuration = NULL; json_object *j_enabled = NULL; json_object *j_plugin = NULL; json_object *j_devices = NULL; char *configuration = NULL; bool enabled = false; char *plugin = NULL; cfg_plugin_configuration *c = NULL; if (!json_object_object_get_ex(j_configurations, name, &j_cfg_plugin)) { LOG("couldn't find configurations section" " for %s.\n", name); return 1; } if (!json_object_object_get_ex(j_cfg_plugin, "configuration", &j_cfg_plugin_configuration)) { LOG("couldn't find %s configuration section.\n", name); return 1; } configuration = (char *)json_object_to_json_string_ext( j_cfg_plugin_configuration, JSON_C_TO_STRING_PLAIN); if (!configuration) { LOG("failed to parse configuration for %s.\n", name); return 1; } configuration = cstr_dup(configuration); if (!configuration) { LOG("cstr_dup failed.\n"); return 1; } if (!json_object_object_get_ex(j_cfg_plugin, "enabled", &j_enabled)) { LOG("couldn't find enabled key" " for %s.\n", name); goto out_free; } if (!json_object_is_type(j_enabled, json_type_boolean)) { LOG("enabled key for %s not boolean.\n", name); goto out_free; } enabled = json_object_get_boolean(j_enabled); if (!json_object_object_get_ex(j_cfg_plugin, "plugin", &j_plugin)) { LOG("couldn't find plugin key" " for %s.\n", name); goto out_free; } if (!json_object_is_type(j_plugin, json_type_string)) { LOG("plugin key for %s not string.\n", name); goto out_free; } plugin = cstr_dup(json_object_get_string(j_plugin)); if (!plugin) { LOG("cstr_dup failed.\n"); goto out_free; } if (!json_object_object_get_ex(j_cfg_plugin, "devices", &j_devices)) { LOG("couldn't find devices key" " for %s.\n", name); goto out_free; } if (!(*list)) { // list is empty c = alloc_configuration(configuration, enabled, plugin, NULL); if (!c) { LOG("malloc failed.\n"); goto out_free; } *list = c; } else { for (c = *list ; c->next ; c = c->next) /* find the end of the list */ ; c->next = alloc_configuration(configuration, enabled, plugin, NULL); if (!c->next) { LOG("malloc failed.\n"); goto out_free; } c = c->next; } c->devices = cfg_process_plugin_devices(name, j_devices); return 0; out_free: if (configuration) free(configuration); if (plugin) free(plugin); if (c) free(c); return 1; } STATIC fpgad_supported_device * cfg_json_to_supported(cfg_plugin_configuration *configurations) { cfg_plugin_configuration *c; cfg_vendor_device_id *d; size_t num_devices = 0; fpgad_supported_device *supported; int i; // find the number of devices for (c = configurations ; c ; c = c->next) { if (!c->enabled) // skip it continue; for (d = c->devices ; d ; d = d->next) { ++num_devices; } } ++num_devices; // +1 for NULL terminator supported = calloc(num_devices, sizeof(fpgad_supported_device)); if (!supported) { LOG("calloc failed.\n"); return NULL; } i = 0; for (c = configurations ; c ; c = c->next) { if (!c->enabled) // skip it continue; for (d = c->devices ; d ; d = d->next) { fpgad_supported_device *dev = &supported[i++]; dev->vendor_id = d->vendor_id; dev->device_id = d->device_id; dev->library_path = cstr_dup(c->library); dev->config = cstr_dup(c->configuration); } } for (c = configurations ; c ; ) { cfg_plugin_configuration *ctrash = c; for (d = c->devices ; d ; ) { cfg_vendor_device_id *dtrash = d; d = d->next; free(dtrash); } c = c->next; if (ctrash->configuration) free(ctrash->configuration); if (ctrash->library) free(ctrash->library); free(ctrash); } return supported; } STATIC bool cfg_verify_supported_devices(fpgad_supported_device *d) { while (d->library_path) { char *sub = NULL; if (d->library_path[0] == '/') { LOG("plugin library paths may not " "be absolute paths: %s\n", d->library_path); return false; } if (cmd_path_is_symlink(d->library_path)) { LOG("plugin library paths may not " "contain links: %s\n", d->library_path); return false; } sub = strstr((char *)d->library_path, ".."); if (sub) { LOG("plugin library paths may not " "contain .. : %s\n", d->library_path); return false; } ++d; } return true; } int cfg_load_config(struct fpgad_config *c) { char *cfg_buf; json_object *root = NULL; json_object *j_configurations = NULL; json_object *j_plugins = NULL; enum json_tokener_error j_err = json_tokener_success; int res = 1; int num_plugins; int i; cfg_plugin_configuration *configurations = NULL; cfg_buf = cfg_read_file(c->cfgfile); if (!cfg_buf) return res; root = json_tokener_parse_verbose(cfg_buf, &j_err); if (!root) { LOG("error parsing %s: %s\n", c->cfgfile, json_tokener_error_desc(j_err)); goto out_free; } if (!json_object_object_get_ex(root, "configurations", &j_configurations)) { LOG("failed to find configurations section in %s.\n", c->cfgfile); goto out_put; } if (!json_object_object_get_ex(root, "plugins", &j_plugins)) { LOG("failed to find plugins section in %s.\n", c->cfgfile); goto out_put; } if (!json_object_is_type(j_plugins, json_type_array)) { LOG("'plugins' JSON object not array.\n"); goto out_put; } num_plugins = json_object_array_length(j_plugins); for (i = 0 ; i < num_plugins ; ++i) { json_object *j_plugin; const char *plugin_name; j_plugin = json_object_array_get_idx(j_plugins, i); plugin_name = json_object_get_string(j_plugin); if (cfg_process_plugin(plugin_name, j_configurations, &configurations)) goto out_put; } if (!configurations) { LOG("no configurations found in %s.\n", c->cfgfile); goto out_put; } c->supported_devices = cfg_json_to_supported(configurations); if (c->supported_devices) { if (cfg_verify_supported_devices(c->supported_devices)) { res = 0; } else { fpgad_supported_device *trash = c->supported_devices; LOG("invalid configuration file\n"); while (trash->library_path) { free((void *)trash->library_path); if (trash->config) free((void *)trash->config); ++trash; } free(c->supported_devices); c->supported_devices = NULL; } } out_put: json_object_put(root); out_free: free(cfg_buf); return res; }