Blob Blame History Raw
// 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 <config.h>
#endif // HAVE_CONFIG_H

#include <sys/types.h>
#include <pwd.h>
#include "config_file.h"
#include "monitored_device.h"
#include "api/sysfs.h"

#include <json-c/json.h>

#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;
}