text: /*
   * zdev - Modify and display the persistent configuration of devices
   *
   * Copyright IBM Corp. 2016, 2019
   *
   * s390-tools is free software; you can redistribute it and/or modify
   * it under the terms of the MIT license. See LICENSE for details.
   */
  
  #include <stdarg.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/stat.h>
  
  #include "lib/util_path.h"
  
  #include "attrib.h"
  #include "device.h"
  #include "devtype.h"
  #include "internal.h"
  #include "misc.h"
  #include "namespace.h"
  #include "setting.h"
  #include "subtype.h"
  #include "udev.h"
  
  /* Create and initialize a new device. */
  struct device *device_new(struct subtype *st, const char *id)
  {
  	struct device *dev;
  	struct namespace *ns = st->namespace;
  
  	dev = misc_malloc(sizeof(struct device));
  	dev->subtype = st;
  
  	dev->id = ns->normalize_id(id);
  	dev->devid = ns->parse_id(id, 0);
  	if (!dev->id || !dev->devid) {
  		device_free(dev);
  		return NULL;
  	}
  
  	dev->active.settings = setting_list_new();
  	dev->persistent.settings = setting_list_new();
  	dev->autoconf.settings = setting_list_new();
  
  	return dev;
  }
  
  /* Release all resources associated with the specified device. */
  void device_free(struct device *dev)
  {
  	if (!dev)
  		return;
  	free(dev->id);
  	free(dev->devid);
  	setting_list_free(dev->active.settings);
  	setting_list_free(dev->persistent.settings);
  	setting_list_free(dev->autoconf.settings);
  	free(dev);
  }
  
  /* Used for debugging. */
  void device_print(struct device *dev, int level)
  {
  	printf("%*sdevice at %p:\n", level, "", (void *) dev);
  	if (!dev)
  		return;
  	printf("%*stype=%s id=%s devid=%p proc=%d\n", level + 4, "",
  	       dev->subtype->name, dev->id, dev->devid, dev->processed);
  
  	printf("%*sactive:\n", level + 4, "");
  	printf("%*sexists=%d mod=%d deconf=%d def=%d blacklisted=%d\n",
  	       level + 8, "", dev->active.exists, dev->active.modified,
  	       dev->active.deconfigured, dev->active.definable,
  	       dev->active.blacklisted);
  	if (dev->active.settings)
  		setting_list_print(dev->active.settings, level + 8);
  	else
  		printf("%*s<none>\n", level + 8, "");
  
  	printf("%*spersistent:\n", level + 4, "");
  	printf("%*sexists=%d mod=%d deconf=%d\n",
  	       level + 8, "", dev->persistent.exists, dev->persistent.modified,
  	       dev->persistent.deconfigured);
  	if (dev->persistent.settings)
  		setting_list_print(dev->persistent.settings, level + 8);
  	else
  		printf("%*s<none>\n", level + 8, "");
  
  	printf("%*sautoconf:\n", level + 4, "");
  	printf("%*sexists=%d mod=%d deconf=%d\n",
  	       level + 8, "", dev->autoconf.exists, dev->autoconf.modified,
  	       dev->autoconf.deconfigured);
  	if (dev->autoconf.settings)
  		setting_list_print(dev->autoconf.settings, level + 8);
  	else
  		printf("%*s<none>\n", level + 8, "");
  }
  
  static const void *device_hash_get_id(void *dev_ptr)
  {
  	struct device *dev = dev_ptr;
  
  	return dev->devid;
  }
  
  /* Create and initialize a new device_list. */
  struct device_list *device_list_new(struct subtype *st)
  {
  	struct device_list *list;
  	struct namespace *ns = st->namespace;
  
  	list = misc_malloc(sizeof(struct device_list));
  	hash_init(&list->hash, ns->hash_buckets, device_hash_get_id,
  		  ns->cmp_parsed_ids, ns->hash_parsed_id,
  		  struct device, node);
  
  	return list;
  }
  
  /* Release resources used by list and enlisted devices. */
  void device_list_free(struct device_list *list)
  {
  	if (!list)
  		return;
  	hash_clear(&list->hash, (void (*)(void *)) device_free);
  	free(list);
  }
  
  /* Add a new element to a list and mark the list as modified. */
  void device_list_add(struct device_list *list, struct device *device)
  {
  	hash_add(&list->hash, device);
  	list->modified = 1;
  }
  
  /* Find an element in the list. */
  struct device *device_list_find(struct device_list *list, const char *id,
  				struct device *start)
  {
  	struct device *dev;
  	struct namespace *ns;
  	void *devid;
  
  	if (!list)
  		return NULL;
  
  	dev = start ? start : util_list_start(&list->hash.list);
  	if (!dev)
  		return NULL;
  
  	ns = dev->subtype->namespace;
  	devid = ns->parse_id(id, err_ignore);
  	if (!devid)
  		goto out;
  
  	/* Try to find using hashed ID data. */
  	if (!start && list->hash.get_hash) {
  		dev = hash_find_by_id(&list->hash, devid);
  		goto out;
  	}
  
  	/* Find the slow way. */
  	while (dev) {
  		if (ns->cmp_parsed_ids(dev->devid, devid) == 0)
  			goto out;
  		dev = util_list_next(&list->hash.list, dev);
  	}
  
  out:
  	free(devid);
  
  	return dev;
  }
  
  /* Used for debugging. */
  void device_list_print(struct device_list *list, int level)
  {
  	struct device *dev;
  
  	printf("%*sdevice list at %p:\n", level, "", (void *) list);
  	util_list_iterate(&list->hash.list, dev)
  		device_print(dev, level + 4);
  }
  
  /* Check if a device configuration needs to be written. */
  bool device_needs_writing(struct device *dev, config_t config)
  {
  	if (SCOPE_ACTIVE(config) &&
  	    (dev->active.modified || dev->active.deconfigured ||
  	     setting_list_modified(dev->active.settings)))
  		return true;
  	if (SCOPE_PERSISTENT(config) &&
  	    (dev->persistent.modified || dev->persistent.deconfigured ||
  	     setting_list_modified(dev->persistent.settings)))
  		return true;
  	if (SCOPE_AUTOCONF(config) &&
  	    (dev->autoconf.modified || dev->autoconf.deconfigured ||
  	     setting_list_modified(dev->autoconf.settings)))
  		return true;
  
  	return false;
  }
  
  static exit_code_t apply_setting(struct device *dev, config_t config,
  				 const char *key, const char *value,
  				 struct util_list *processed)
  {
  	struct subtype *st = dev->subtype;
  	struct attrib *a;
  	struct setting *s;
  	bool warn_readonly = false;
  
  	/* Check for known attribute. */
  	a = subtype_find_dev_attrib(st, key);
  	if (a) {
  		/* Check for acceptable value of known attribute. */
  		if (!force && !attrib_check_value(a, value))
  			goto err_invalid_forceable;
  		/* Check for activeonly. */
  		if (!force && SCOPE_PERSISTENT(config) && a->activeonly)
  			goto err_activeonly_forceable;
  		if (!force && SCOPE_AUTOCONF(config) && a->activeonly)
  			goto err_activeonly_forceable;
  		/* Check for internal. */
  		if (config == config_active && a->internal)
  			goto err_int_noactive;
  		/* Check for multiple values. */
  		if (!force && !a->multi && strlist_find(processed, key))
  			goto err_multi_forceable;
  	} else {
  		/* Handle unknown attribute. */
  		if (!st->unknown_dev_attribs)
  			goto err_unknown;
  		if (!force)
  			goto err_unknown_forceable;
  		/* Check for internal. */
  		if (config == config_active && internal_by_name(key))
  			goto err_int_noactive;
  	}
  
  	strlist_add(processed, "%s", key);
  
  	/* Apply to active config. */
  	if (SCOPE_ACTIVE(config)) {
  		s = setting_list_apply_specified(dev->active.settings, a,
  						 key, value);
  		if (s->readonly)
  			warn_readonly = true;
  	}
  
  	/* Apply to persistent config. */
  	if (SCOPE_PERSISTENT(config)) {
  		setting_list_apply_specified(dev->persistent.settings,
  					     a, key, value);
  	}
  
  	/* Apply to autoconf config. */
  	if (SCOPE_AUTOCONF(config)) {
  		setting_list_apply_specified(dev->autoconf.settings,
  					     a, key, value);
  	}
  
  	/* Additional warning when trying to persist read-only setting. */
  	if (!SCOPE_ACTIVE(config) && (SCOPE_PERSISTENT(config) ||
  				      SCOPE_AUTOCONF(config))) {
  		s = setting_list_find(dev->active.settings, key);
  		if (s && s->readonly)
  			warn_readonly = true;
  	}
  
  	if (warn_readonly)
  		delayed_warn("Modifying read-only attribute: %s\n", key);
  
  	return EXIT_OK;
  
  err_invalid_forceable:
  	delayed_forceable("Invalid value for %s attribute: %s=%s\n",
  			  dev->subtype->name, key, value);
  	delayed_info("Acceptable values:\n");
  	attrib_print_acceptable(a, -1);
  	delayed_info("Use '%s %s --help-attribute %s' for more "
  		     "information\n", toolname, dev->subtype->name, key);
  	return EXIT_INVALID_SETTING;
  
  err_multi_forceable:
  	delayed_forceable("Cannot specify multiple values for attribute '%s'\n",
  			  key);
  	return EXIT_INVALID_SETTING;
  
  err_unknown:
  	delayed_err("Unknown %s attribute specified: %s\n", st->devname, key);
  	return EXIT_ATTRIB_NOT_FOUND;
  
  err_unknown_forceable:
  	delayed_forceable("Unknown %s attribute specified: %s\n",
  			  st->devname, key);
  	return EXIT_ATTRIB_NOT_FOUND;
  
  err_activeonly_forceable:
  	delayed_forceable("Attribute '%s' should only be changed in the active "
  			  "config\n", a->name);
  	return EXIT_INVALID_SETTING;
  
  err_int_noactive:
  	delayed_err("Internal attribute '%s' cannot be set in the active config\n",
  		    key);
  	return EXIT_INVALID_SETTING;
  }
  
  /* Apply device settings from strlist to device. */
  exit_code_t device_apply_strlist(struct device *dev, config_t config,
  				 struct util_list *settings)
  {
  	struct util_list *processed;
  	struct strlist_node *s;
  	exit_code_t rc;
  	char *key, *value;
  
  	/* Apply settings. */
  	processed = strlist_new();
  	rc = EXIT_OK;
  	util_list_iterate(settings, s) {
  		key = misc_strdup(s->str);
  		value = strchr(key, '=');
  		*value = 0;
  		value++;
  
  		rc = apply_setting(dev, config, key, value, processed);
  		free(key);
  
  		if (rc)
  			break;
  	}
  
  	strlist_free(processed);
  
  	return rc;
  }
  
  /* Apply device settings from setting_list to device. */
  exit_code_t device_apply_settings(struct device *dev, config_t config,
  				  struct util_list *settings)
  {
  	struct util_list *processed;
  	struct setting *s;
  	exit_code_t rc;
  
  	/* Apply settings. */
  	processed = strlist_new();
  	rc = EXIT_OK;
  	util_list_iterate(settings, s) {
  		rc = apply_setting(dev, config, s->name, s->value, processed);
  		if (rc)
  			break;
  	}
  
  	strlist_free(processed);
  
  	return rc;
  }
  
  static void reset_device_state(struct device_state *state)
  {
  	setting_list_free(state->settings);
  	state->settings = setting_list_new();
  	state->exists = 0;
  	state->modified = 0;
  	state->deconfigured = 0;
  	state->definable = 0;
  	state->blacklisted = 0;
  }
  
  void device_reset(struct device *dev, config_t config)
  {
  	if (SCOPE_ACTIVE(config))
  		reset_device_state(&dev->active);
  	if (SCOPE_PERSISTENT(config))
  		reset_device_state(&dev->persistent);
  	if (SCOPE_AUTOCONF(config))
  		reset_device_state(&dev->autoconf);
  	dev->processed = 0;
  }
  
  void device_add_modules(struct util_list *modules, struct device *dev)
  {
  	struct subtype *st = dev->subtype;
  
  	/* Add dynamic module info. */
  	subtype_add_modules(st, dev, modules);
  
  	/* Add static subtype module info. */
  	subtype_add_static_modules(modules, st);
  
  	/* Add static devtype module info. */
  	devtype_add_modules(modules, st->devtype, 0);
  }
  
  char *device_read_active_attrib(struct device *dev, const char *name)
  {
  	struct subtype *st = dev->subtype;
  	char *path, *value, *link;
  
  	/* Try direct approach. */
  	value = subtype_get_active_attrib(st, dev, name);
  	if (value)
  		return value;
  
  	/* Try reading from path. */
  	path = subtype_get_active_attrib_path(st, dev, name);
  	if (!path)
  		return NULL;
  	value = misc_read_text_file(path, 1, err_ignore);
  	if (!value) {
  		/* Symbolic links count as read-only attributes. */
  		link = misc_readlink(path);
  		if (link) {
  			value = misc_strdup(basename(link));
  			free(link);
  		}
  	}
  	free(path);
  
  	return value;
  }
  
  /* Return a newly allocated strlist of attribute names for @dev based on
   * @scope. */
  static struct util_list *get_attrib_names(struct device *dev,
  					  read_scope_t scope)
  {
  	struct subtype *st = dev->subtype;
  	struct util_list *names, *files;
  	struct strlist_node *s;
  	int i;
  	struct attrib *a;
  	const char *prefix;
  	char *path;
  
  	names = strlist_new();
  
  	/* Start with known and mandatory attributes. */
  	for (i = 0; (a = st->dev_attribs[i]); i++) {
  		if (scope != scope_mandatory || a->mandatory)
  			strlist_add(names, a->name);
  	}
  	if (scope != scope_all)
  		goto out;
  
  	/* Add attributes based on readable files in any of the prefix
  	 * directories. */
  	prefix = "";
  	i = 0;
  	do {
  		path = subtype_get_active_attrib_path(st, dev, prefix);
  		if (!path)
  			continue;
  
  		/* Add attribute name for each file in path. */
  		files = strlist_new();
  		misc_read_dir(path, files, NULL, NULL);
  		util_list_iterate(files, s) {
  			if (*prefix) {
  				strlist_add_unique(names, "%s/%s", prefix,
  						   s->str);
  			} else
  				strlist_add_unique(names, s->str);
  		}
  		strlist_free(files);
  
  		free(path);
  	} while (st->prefixes && (prefix = st->prefixes[i++]));
  
  out:
  	return names;
  }
  
  /* Read settings according to @scope for device @dev from active
   * configuration and add them to dev->active.settings. */
  void device_read_active_settings(struct device *dev, read_scope_t scope)
  {
  	struct subtype *st = dev->subtype;
  	struct util_list *names;
  	struct strlist_node *str;
  	char *path, *name, *value, *link;
  	struct attrib *a;
  	struct setting *s;
  
  	/* Expand scope. */
  	names = get_attrib_names(dev, scope);
  
  	util_list_iterate(names, str) {
  		name = str->str;
  
  		/* Don't add uevent attribute. */
  		if (strcmp(name, "uevent") == 0 || ends_with(name, "/uevent"))
  			continue;
  
  		/* Determine full attribute path. */
  		path = subtype_get_active_attrib_path(st, dev, name);
  		if (!path)
  			continue;
  
  		/* Get attribute value. */
  		link = NULL;
  		value = misc_read_text_file(path, 1, err_ignore);
  		if (!value) {
  			if (scope != scope_all)
  				goto next;
  
  			/* Register symbolic links as readonly attributes. */
  			link = misc_readlink(path);
  			if (!link)
  				goto next;
  
  			value = basename(link);
  		}
  
  		/* Apply setting to list. */
  		a = attrib_find(st->dev_attribs, name);
  		s = setting_list_apply_actual(dev->active.settings, a, name,
  					      value);
  		if (link || (scope == scope_all &&
  			     util_path_is_readonly_file("%s", path)))
  			s->readonly = 1;
  		if (link)
  			free(link);
  		else
  			free(value);
  next:
  		free(path);
  	}
  
  	strlist_free(names);
  }
  
  /* Apply modified settings in @dev->active.settings to active configuration.
   * Abort on first error. This requires that @dev defines
   * get_active_attrib_path. */
  exit_code_t device_write_active_settings(struct device *dev)
  {
  	struct subtype *st = dev->subtype;
  	struct util_list *list;
  	struct ptrlist_node *p;
  	struct setting *s;
  	char *path;
  	exit_code_t rc = EXIT_OK;
  
  	/* Get order of applying attributes. */
  	list = setting_list_get_sorted(dev->active.settings);
  
  	/* Apply settings in order. */
  	util_list_iterate(list, p) {
  		s = p->ptr;
  		if (!s->modified || s->removed)
  			continue;
  		if ((s->attrib && s->attrib->internal) ||
  		    internal_by_name(s->name))
  			continue;
  
  		path = subtype_get_active_attrib_path(st, dev, s->name);
  		if (!path) {
  			delayed_err("Could not determine path for attribute "
  				    "'%s'\n", s->name);
  			rc = EXIT_SETTING_NOT_FOUND;
  			break;
  		}
  		rc = setting_write(path, s);
  		free(path);
  		if (rc)
  			break;
  	}
  
  	ptrlist_free(list, 0);
  
  	/* Changing device configuration could generate uevents. */
  	udev_need_settle = 1;
  
  	return rc;
  }
  
  /* Check if there are any conflicts in the settings list of @dev for the
   * specified configuration. */
  exit_code_t device_check_settings(struct device *dev, config_t config,
  				  err_t err)
  {
  	struct setting *s;
  
  	if (SCOPE_ACTIVE(config)) {
  		/* Ensure that actual values are available or we might
  		 * report false positives. */
  		util_list_iterate(&dev->active.settings->list, s) {
  			if (s->actual_value || s->removed ||
  			    (!s->modified && !s->specified))
  				continue;
  			s->actual_value =
  				device_read_active_attrib(dev, s->name);
  		}
  
  		if (!setting_list_check_conflict(dev->active.settings,
  						 config_active,
  						 err))
  			return EXIT_INVALID_CONFIG;
  	}
  	if (SCOPE_PERSISTENT(config)) {
  		if (!setting_list_check_conflict(dev->persistent.settings,
  						 config_persistent,
  						 err))
  			return EXIT_INVALID_CONFIG;
  	}
  	if (SCOPE_AUTOCONF(config)) {
  		if (!setting_list_check_conflict(dev->autoconf.settings,
  						 config_autoconf,
  						 err))
  			return EXIT_INVALID_CONFIG;
  	}
  
  	return EXIT_OK;
  }
  
  struct setting_list *device_get_setting_list(struct device *dev,
  					     config_t config)
  {
  	struct setting_list *settings = NULL;
  
  	if (config == config_active)
  		settings = dev->active.settings;
  	else if (config == config_persistent)
  		settings = dev->persistent.settings;
  	else if (config == config_autoconf)
  		settings = dev->autoconf.settings;
  
  	return settings;
  }
  
  /* Return configuration set in which device exists. */
  config_t device_get_config(struct device *dev)
  {
  	config_t config = 0;
  
  	if (dev->active.exists || dev->active.definable)
  		config |= config_active;
  
  	if (dev->persistent.exists)
  		config |= config_persistent;
  
  	if (dev->autoconf.exists)
  		config |= config_autoconf;
  
  	return config;
  }
  