mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
Avoid code duplication and inconsistent error handling by replacing readlink() with util_readlink(), which is used project-wide to standardize readlink() usage. Reviewed-by: Jan Höppner <hoeppner@linux.ibm.com> Signed-off-by: Jan Polensky <japo@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
729 lines
18 KiB
C
729 lines
18 KiB
C
/*
|
|
* 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 <libgen.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "lib/util_libc.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;
|
|
int i;
|
|
|
|
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();
|
|
|
|
for (i = 0; i < NUM_SITES; i++)
|
|
dev->site_specific[i].settings = setting_list_new();
|
|
|
|
return dev;
|
|
}
|
|
|
|
/* Release all resources associated with the specified device. */
|
|
void device_free(struct device *dev)
|
|
{
|
|
int i;
|
|
|
|
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);
|
|
|
|
for (i = 0; i < NUM_SITES; i++)
|
|
setting_list_free(dev->site_specific[i].settings);
|
|
|
|
free(dev);
|
|
}
|
|
|
|
/* Used for debugging. */
|
|
void device_print(struct device *dev, int level)
|
|
{
|
|
int i;
|
|
|
|
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, "");
|
|
|
|
printf("%*spersistent-site-specific:\n", level + 4, "");
|
|
for (i = 0; i < NUM_SITES; i++) {
|
|
printf("%*sexists=%d mod=%d deconf=%d\n",
|
|
level + 8, "", dev->site_specific[i].exists,
|
|
dev->site_specific[i].modified,
|
|
dev->site_specific[i].deconfigured);
|
|
if (dev->site_specific[i].settings)
|
|
setting_list_print(dev->site_specific[i].settings, level + 8);
|
|
else
|
|
printf("%*s<none>\n", level + 8, "");
|
|
}
|
|
}
|
|
|
|
char *device_get_sites(struct device *dev)
|
|
{
|
|
struct util_list *names;
|
|
char *str;
|
|
int i;
|
|
|
|
names = strlist_new();
|
|
for (i = 0; i < NUM_USER_SITES; i++) {
|
|
if (dev->site_specific[i].exists)
|
|
strlist_add(names, "%d", i);
|
|
}
|
|
|
|
str = strlist_flatten(names, ",");
|
|
strlist_free(names);
|
|
|
|
return str;
|
|
}
|
|
|
|
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 read-only attribute. */
|
|
if (a->readonly)
|
|
goto err_readonly;
|
|
/* 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;
|
|
|
|
err_readonly:
|
|
delayed_err("Cannot modify read-only attribute: %s\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 = util_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 = util_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 ((a && a->readonly) || 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,
|
|
int site_id)
|
|
{
|
|
struct setting_list *settings = NULL;
|
|
|
|
if (site_id < SITE_FALLBACK) {
|
|
settings = dev->site_specific[site_id].settings;
|
|
goto out;
|
|
}
|
|
|
|
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;
|
|
|
|
out:
|
|
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;
|
|
}
|
|
|
|
/* Determine if persistent configuration available for any of the sites*/
|
|
bool is_dev_pers(struct device *dev)
|
|
{
|
|
int i;
|
|
|
|
if (global_site_id != SITE_FALLBACK &&
|
|
!dev->site_specific[global_site_id].exists)
|
|
return false;
|
|
else if (dev->persistent.exists)
|
|
return true;
|
|
|
|
for (i = 0; i < NUM_SITES; i++)
|
|
if (dev->site_specific[i].exists)
|
|
return true;
|
|
|
|
return false;
|
|
}
|