diff options
Diffstat (limited to 'roms/ipxe/src/core/settings.c')
-rw-r--r-- | roms/ipxe/src/core/settings.c | 2556 |
1 files changed, 2556 insertions, 0 deletions
diff --git a/roms/ipxe/src/core/settings.c b/roms/ipxe/src/core/settings.c new file mode 100644 index 00000000..5e16b27d --- /dev/null +++ b/roms/ipxe/src/core/settings.c @@ -0,0 +1,2556 @@ +/* + * Copyright (C) 2008 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <byteswap.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/in.h> +#include <ipxe/ip.h> +#include <ipxe/ipv6.h> +#include <ipxe/vsprintf.h> +#include <ipxe/dhcp.h> +#include <ipxe/uuid.h> +#include <ipxe/uri.h> +#include <ipxe/base16.h> +#include <ipxe/pci.h> +#include <ipxe/init.h> +#include <ipxe/version.h> +#include <ipxe/settings.h> + +/** @file + * + * Configuration settings + * + */ + +/****************************************************************************** + * + * Generic settings blocks + * + ****************************************************************************** + */ + +/** + * A generic setting + * + */ +struct generic_setting { + /** List of generic settings */ + struct list_head list; + /** Setting */ + struct setting setting; + /** Size of setting name */ + size_t name_len; + /** Size of setting data */ + size_t data_len; +}; + +/** + * Get generic setting name + * + * @v generic Generic setting + * @ret name Generic setting name + */ +static inline void * generic_setting_name ( struct generic_setting *generic ) { + return ( ( ( void * ) generic ) + sizeof ( *generic ) ); +} + +/** + * Get generic setting data + * + * @v generic Generic setting + * @ret data Generic setting data + */ +static inline void * generic_setting_data ( struct generic_setting *generic ) { + return ( ( ( void * ) generic ) + sizeof ( *generic ) + + generic->name_len ); +} + +/** + * Find generic setting + * + * @v generics Generic settings block + * @v setting Setting to find + * @ret generic Generic setting, or NULL + */ +static struct generic_setting * +find_generic_setting ( struct generic_settings *generics, + const struct setting *setting ) { + struct generic_setting *generic; + + list_for_each_entry ( generic, &generics->list, list ) { + if ( setting_cmp ( &generic->setting, setting ) == 0 ) + return generic; + } + return NULL; +} + +/** + * Store value of generic setting + * + * @v settings Settings block + * @v setting Setting to store + * @v data Setting data, or NULL to clear setting + * @v len Length of setting data + * @ret rc Return status code + */ +int generic_settings_store ( struct settings *settings, + const struct setting *setting, + const void *data, size_t len ) { + struct generic_settings *generics = + container_of ( settings, struct generic_settings, settings ); + struct generic_setting *old; + struct generic_setting *new = NULL; + size_t name_len; + + /* Identify existing generic setting, if any */ + old = find_generic_setting ( generics, setting ); + + /* Create new generic setting, if required */ + if ( len ) { + /* Allocate new generic setting */ + name_len = ( strlen ( setting->name ) + 1 ); + new = zalloc ( sizeof ( *new ) + name_len + len ); + if ( ! new ) + return -ENOMEM; + + /* Populate new generic setting */ + new->name_len = name_len; + new->data_len = len; + memcpy ( &new->setting, setting, sizeof ( new->setting ) ); + new->setting.name = generic_setting_name ( new ); + memcpy ( generic_setting_name ( new ), + setting->name, name_len ); + memcpy ( generic_setting_data ( new ), data, len ); + } + + /* Delete existing generic setting, if any */ + if ( old ) { + list_del ( &old->list ); + free ( old ); + } + + /* Add new setting to list, if any */ + if ( new ) + list_add ( &new->list, &generics->list ); + + return 0; +} + +/** + * Fetch value of generic setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +int generic_settings_fetch ( struct settings *settings, + struct setting *setting, + void *data, size_t len ) { + struct generic_settings *generics = + container_of ( settings, struct generic_settings, settings ); + struct generic_setting *generic; + + /* Find generic setting */ + generic = find_generic_setting ( generics, setting ); + if ( ! generic ) + return -ENOENT; + + /* Copy out generic setting data */ + if ( len > generic->data_len ) + len = generic->data_len; + memcpy ( data, generic_setting_data ( generic ), len ); + + /* Set setting type, if not yet specified */ + if ( ! setting->type ) + setting->type = generic->setting.type; + + return generic->data_len; +} + +/** + * Clear generic settings block + * + * @v settings Settings block + */ +void generic_settings_clear ( struct settings *settings ) { + struct generic_settings *generics = + container_of ( settings, struct generic_settings, settings ); + struct generic_setting *generic; + struct generic_setting *tmp; + + list_for_each_entry_safe ( generic, tmp, &generics->list, list ) { + list_del ( &generic->list ); + free ( generic ); + } + assert ( list_empty ( &generics->list ) ); +} + +/** Generic settings operations */ +struct settings_operations generic_settings_operations = { + .store = generic_settings_store, + .fetch = generic_settings_fetch, + .clear = generic_settings_clear, +}; + +/****************************************************************************** + * + * Registered settings blocks + * + ****************************************************************************** + */ + +/** Root generic settings block */ +struct generic_settings generic_settings_root = { + .settings = { + .refcnt = NULL, + .name = "", + .siblings = + LIST_HEAD_INIT ( generic_settings_root.settings.siblings ), + .children = + LIST_HEAD_INIT ( generic_settings_root.settings.children ), + .op = &generic_settings_operations, + }, + .list = LIST_HEAD_INIT ( generic_settings_root.list ), +}; + +/** Root settings block */ +#define settings_root generic_settings_root.settings + +/** Autovivified settings block */ +struct autovivified_settings { + /** Reference count */ + struct refcnt refcnt; + /** Generic settings block */ + struct generic_settings generic; +}; + +/** + * Free autovivified settings block + * + * @v refcnt Reference count + */ +static void autovivified_settings_free ( struct refcnt *refcnt ) { + struct autovivified_settings *autovivified = + container_of ( refcnt, struct autovivified_settings, refcnt ); + + generic_settings_clear ( &autovivified->generic.settings ); + free ( autovivified ); +} + +/** + * Find child settings block + * + * @v parent Parent settings block + * @v name Name within this parent + * @ret settings Settings block, or NULL + */ +struct settings * find_child_settings ( struct settings *parent, + const char *name ) { + struct settings *settings; + + /* Find target parent settings block */ + parent = settings_target ( parent ); + + /* Treat empty name as meaning "this block" */ + if ( ! *name ) + return parent; + + /* Look for child with matching name */ + list_for_each_entry ( settings, &parent->children, siblings ) { + if ( strcmp ( settings->name, name ) == 0 ) + return settings_target ( settings ); + } + + return NULL; +} + +/** + * Find or create child settings block + * + * @v parent Parent settings block + * @v name Name within this parent + * @ret settings Settings block, or NULL + */ +struct settings * autovivify_child_settings ( struct settings *parent, + const char *name ) { + struct { + struct autovivified_settings autovivified; + char name[ strlen ( name ) + 1 /* NUL */ ]; + } *new_child; + struct settings *settings; + + /* Find target parent settings block */ + parent = settings_target ( parent ); + + /* Return existing settings, if existent */ + if ( ( settings = find_child_settings ( parent, name ) ) != NULL ) + return settings; + + /* Create new generic settings block */ + new_child = zalloc ( sizeof ( *new_child ) ); + if ( ! new_child ) { + DBGC ( parent, "Settings %p could not create child %s\n", + parent, name ); + return NULL; + } + memcpy ( new_child->name, name, sizeof ( new_child->name ) ); + ref_init ( &new_child->autovivified.refcnt, + autovivified_settings_free ); + generic_settings_init ( &new_child->autovivified.generic, + &new_child->autovivified.refcnt ); + settings = &new_child->autovivified.generic.settings; + register_settings ( settings, parent, new_child->name ); + return settings; +} + +/** + * Return settings block name + * + * @v settings Settings block + * @ret name Settings block name + */ +const char * settings_name ( struct settings *settings ) { + static char buf[16]; + char tmp[ sizeof ( buf ) ]; + + /* Find target settings block */ + settings = settings_target ( settings ); + + /* Construct name */ + for ( buf[2] = buf[0] = 0 ; settings ; settings = settings->parent ) { + memcpy ( tmp, buf, sizeof ( tmp ) ); + snprintf ( buf, sizeof ( buf ), ".%s%s", settings->name, tmp ); + } + return ( buf + 2 ); +} + +/** + * Parse settings block name + * + * @v name Name + * @v get_child Function to find or create child settings block + * @ret settings Settings block, or NULL + */ +static struct settings * +parse_settings_name ( const char *name, get_child_settings_t get_child ) { + struct settings *settings = &settings_root; + char name_copy[ strlen ( name ) + 1 ]; + char *subname; + char *remainder; + + /* Create modifiable copy of name */ + memcpy ( name_copy, name, sizeof ( name_copy ) ); + remainder = name_copy; + + /* Parse each name component in turn */ + while ( remainder ) { + subname = remainder; + remainder = strchr ( subname, '.' ); + if ( remainder ) + *(remainder++) = '\0'; + settings = get_child ( settings, subname ); + if ( ! settings ) + break; + } + + return settings; +} + +/** + * Find settings block + * + * @v name Name + * @ret settings Settings block, or NULL + */ +struct settings * find_settings ( const char *name ) { + + return parse_settings_name ( name, find_child_settings ); +} + +/** + * Apply all settings + * + * @ret rc Return status code + */ +static int apply_settings ( void ) { + struct settings_applicator *applicator; + int rc; + + /* Call all settings applicators */ + for_each_table_entry ( applicator, SETTINGS_APPLICATORS ) { + if ( ( rc = applicator->apply() ) != 0 ) { + DBG ( "Could not apply settings using applicator " + "%p: %s\n", applicator, strerror ( rc ) ); + return rc; + } + } + + return 0; +} + +/** + * Reprioritise settings + * + * @v settings Settings block + * + * Reorders the settings block amongst its siblings according to its + * priority. + */ +static void reprioritise_settings ( struct settings *settings ) { + struct settings *parent = settings->parent; + long priority; + struct settings *tmp; + long tmp_priority; + + /* Stop when we reach the top of the tree */ + if ( ! parent ) + return; + + /* Read priority, if present */ + priority = fetch_intz_setting ( settings, &priority_setting ); + + /* Remove from siblings list */ + list_del ( &settings->siblings ); + + /* Reinsert after any existing blocks which have a higher priority */ + list_for_each_entry ( tmp, &parent->children, siblings ) { + tmp_priority = fetch_intz_setting ( tmp, &priority_setting ); + if ( priority > tmp_priority ) + break; + } + list_add_tail ( &settings->siblings, &tmp->siblings ); + + /* Recurse up the tree */ + reprioritise_settings ( parent ); +} + +/** + * Register settings block + * + * @v settings Settings block + * @v parent Parent settings block, or NULL + * @v name Settings block name + * @ret rc Return status code + */ +int register_settings ( struct settings *settings, struct settings *parent, + const char *name ) { + struct settings *old_settings; + + /* Sanity check */ + assert ( settings != NULL ); + + /* Find target parent settings block */ + parent = settings_target ( parent ); + + /* Apply settings block name */ + settings->name = name; + + /* Remove any existing settings with the same name */ + if ( ( old_settings = find_child_settings ( parent, settings->name ) )) + unregister_settings ( old_settings ); + + /* Add to list of settings */ + ref_get ( settings->refcnt ); + ref_get ( parent->refcnt ); + settings->parent = parent; + list_add_tail ( &settings->siblings, &parent->children ); + DBGC ( settings, "Settings %p (\"%s\") registered\n", + settings, settings_name ( settings ) ); + + /* Fix up settings priority */ + reprioritise_settings ( settings ); + + /* Apply potentially-updated settings */ + apply_settings(); + + return 0; +} + +/** + * Unregister settings block + * + * @v settings Settings block + */ +void unregister_settings ( struct settings *settings ) { + struct settings *child; + struct settings *tmp; + + /* Unregister child settings */ + list_for_each_entry_safe ( child, tmp, &settings->children, siblings ) { + unregister_settings ( child ); + } + + DBGC ( settings, "Settings %p (\"%s\") unregistered\n", + settings, settings_name ( settings ) ); + + /* Remove from list of settings */ + ref_put ( settings->parent->refcnt ); + settings->parent = NULL; + list_del ( &settings->siblings ); + ref_put ( settings->refcnt ); + + /* Apply potentially-updated settings */ + apply_settings(); +} + +/****************************************************************************** + * + * Core settings routines + * + ****************************************************************************** + */ + +/** + * Redirect to target settings block + * + * @v settings Settings block, or NULL + * @ret settings Underlying settings block + */ +struct settings * settings_target ( struct settings *settings ) { + + /* NULL settings implies the global settings root */ + if ( ! settings ) + settings = &settings_root; + + /* Redirect to underlying settings block, if applicable */ + if ( settings->op->redirect ) + return settings->op->redirect ( settings ); + + /* Otherwise, return this settings block */ + return settings; +} + +/** + * Check applicability of setting + * + * @v settings Settings block + * @v setting Setting + * @ret applies Setting applies within this settings block + */ +int setting_applies ( struct settings *settings, + const struct setting *setting ) { + + /* Find target settings block */ + settings = settings_target ( settings ); + + /* Check applicability of setting */ + return ( settings->op->applies ? + settings->op->applies ( settings, setting ) : 1 ); +} + +/** + * Find setting applicable to settings block, if any + * + * @v settings Settings block + * @v setting Setting + * @ret setting Applicable setting, if any + */ +static const struct setting * +applicable_setting ( struct settings *settings, const struct setting *setting ){ + const struct setting *applicable; + + /* If setting is already applicable, use it */ + if ( setting_applies ( settings, setting ) ) + return setting; + + /* Otherwise, look for a matching predefined setting which does apply */ + for_each_table_entry ( applicable, SETTINGS ) { + if ( ( setting_cmp ( setting, applicable ) == 0 ) && + ( setting_applies ( settings, applicable ) ) ) + return applicable; + } + + return NULL; +} + +/** + * Store value of setting + * + * @v settings Settings block, or NULL + * @v setting Setting to store + * @v data Setting data, or NULL to clear setting + * @v len Length of setting data + * @ret rc Return status code + */ +int store_setting ( struct settings *settings, const struct setting *setting, + const void *data, size_t len ) { + int rc; + + /* Find target settings block */ + settings = settings_target ( settings ); + + /* Fail if setting does not apply to this settings block */ + if ( ! setting_applies ( settings, setting ) ) + return -ENOTTY; + + /* Sanity check */ + if ( ! settings->op->store ) + return -ENOTSUP; + + /* Store setting */ + if ( ( rc = settings->op->store ( settings, setting, + data, len ) ) != 0 ) + return rc; + + /* Reprioritise settings if necessary */ + if ( setting_cmp ( setting, &priority_setting ) == 0 ) + reprioritise_settings ( settings ); + + /* If these settings are registered, apply potentially-updated + * settings + */ + for ( ; settings ; settings = settings->parent ) { + if ( settings == &settings_root ) { + if ( ( rc = apply_settings() ) != 0 ) + return rc; + break; + } + } + + return 0; +} + +/** + * Fetch setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v origin Origin of setting to fill in, or NULL + * @v fetched Fetched setting to fill in, or NULL + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + * + * The actual length of the setting will be returned even if + * the buffer was too small. + */ +int fetch_setting ( struct settings *settings, const struct setting *setting, + struct settings **origin, struct setting *fetched, + void *data, size_t len ) { + const struct setting *applicable; + struct settings *child; + struct setting tmp; + int ret; + + /* Avoid returning uninitialised data on error */ + memset ( data, 0, len ); + if ( origin ) + *origin = NULL; + if ( fetched ) + memcpy ( fetched, setting, sizeof ( *fetched ) ); + + /* Find target settings block */ + settings = settings_target ( settings ); + + /* Sanity check */ + if ( ! settings->op->fetch ) + return -ENOTSUP; + + /* Try this block first, if an applicable setting exists */ + if ( ( applicable = applicable_setting ( settings, setting ) ) ) { + + /* Create modifiable copy of setting */ + memcpy ( &tmp, applicable, sizeof ( tmp ) ); + if ( ( ret = settings->op->fetch ( settings, &tmp, + data, len ) ) >= 0 ) { + + /* Default to string type, if not yet specified */ + if ( ! tmp.type ) + tmp.type = &setting_type_string; + + /* Record origin, if applicable */ + if ( origin ) + *origin = settings; + + /* Record fetched setting, if applicable */ + if ( fetched ) + memcpy ( fetched, &tmp, sizeof ( *fetched ) ); + + return ret; + } + } + + /* Recurse into each child block in turn */ + list_for_each_entry ( child, &settings->children, siblings ) { + if ( ( ret = fetch_setting ( child, setting, origin, fetched, + data, len ) ) >= 0 ) + return ret; + } + + return -ENOENT; +} + +/** + * Fetch allocated copy of setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v origin Origin of setting to fill in, or NULL + * @v fetched Fetched setting to fill in, or NULL + * @v data Buffer to allocate and fill with setting data + * @v alloc Allocation function + * @ret len Length of setting, or negative error + * + * The caller is responsible for eventually freeing the allocated + * buffer. + */ +static int fetch_setting_alloc ( struct settings *settings, + const struct setting *setting, + struct settings **origin, + struct setting *fetched, + void **data, + void * ( * alloc ) ( size_t len ) ) { + struct settings *tmp_origin; + struct setting tmp_fetched; + int len; + int check_len; + + /* Use local buffers if necessary */ + if ( ! origin ) + origin = &tmp_origin; + if ( ! fetched ) + fetched = &tmp_fetched; + + /* Avoid returning uninitialised data on error */ + *data = NULL; + + /* Check existence, and fetch setting length */ + len = fetch_setting ( settings, setting, origin, fetched, NULL, 0 ); + if ( len < 0 ) + return len; + + /* Allocate buffer */ + *data = alloc ( len ); + if ( ! *data ) + return -ENOMEM; + + /* Fetch setting value */ + check_len = fetch_setting ( *origin, fetched, NULL, NULL, *data, len ); + assert ( check_len == len ); + return len; +} + +/** + * Fetch copy of setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v origin Origin of setting to fill in, or NULL + * @v fetched Fetched setting to fill in, or NULL + * @v data Buffer to allocate and fill with setting data + * @ret len Length of setting, or negative error + * + * The caller is responsible for eventually freeing the allocated + * buffer. + */ +int fetch_setting_copy ( struct settings *settings, + const struct setting *setting, + struct settings **origin, struct setting *fetched, + void **data ) { + + return fetch_setting_alloc ( settings, setting, origin, fetched, + data, malloc ); +} + +/** + * Fetch value of setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v data Buffer to fill with setting string data + * @v len Length of buffer + * @ret len Length of setting, or negative error + */ +int fetch_raw_setting ( struct settings *settings, + const struct setting *setting, + void *data, size_t len ) { + + return fetch_setting ( settings, setting, NULL, NULL, data, len ); +} + +/** + * Fetch value of setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v data Buffer to allocate and fill with setting data + * @ret len Length of setting, or negative error + * + * The caller is responsible for eventually freeing the allocated + * buffer. + */ +int fetch_raw_setting_copy ( struct settings *settings, + const struct setting *setting, + void **data ) { + + return fetch_setting_copy ( settings, setting, NULL, NULL, data ); +} + +/** + * Fetch value of string setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v data Buffer to fill with setting string data + * @v len Length of buffer + * @ret len Length of string setting, or negative error + * + * The resulting string is guaranteed to be correctly NUL-terminated. + * The returned length will be the length of the underlying setting + * data. + */ +int fetch_string_setting ( struct settings *settings, + const struct setting *setting, + char *data, size_t len ) { + + memset ( data, 0, len ); + return fetch_raw_setting ( settings, setting, data, + ( ( len > 0 ) ? ( len - 1 ) : 0 ) ); +} + +/** + * Allocate memory for copy of string setting + * + * @v len Length of setting + * @ret ptr Allocated memory + */ +static void * fetch_string_setting_copy_alloc ( size_t len ) { + return zalloc ( len + 1 /* NUL */ ); +} + +/** + * Fetch value of string setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v data Buffer to allocate and fill with setting string data + * @ret len Length of string setting, or negative error + * + * The resulting string is guaranteed to be correctly NUL-terminated. + * The returned length will be the length of the underlying setting + * data. The caller is responsible for eventually freeing the + * allocated buffer. + */ +int fetch_string_setting_copy ( struct settings *settings, + const struct setting *setting, char **data ) { + + return fetch_setting_alloc ( settings, setting, NULL, NULL, + ( ( void ** ) data ), + fetch_string_setting_copy_alloc ); +} + +/** + * Fetch value of IPv4 address setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v inp IPv4 addresses to fill in + * @v count Maximum number of IPv4 addresses + * @ret len Length of setting, or negative error + */ +int fetch_ipv4_array_setting ( struct settings *settings, + const struct setting *setting, + struct in_addr *inp, unsigned int count ) { + int len; + + len = fetch_raw_setting ( settings, setting, inp, + ( sizeof ( *inp ) * count ) ); + if ( len < 0 ) + return len; + if ( ( len % sizeof ( *inp ) ) != 0 ) + return -ERANGE; + return len; +} + +/** + * Fetch value of IPv4 address setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v inp IPv4 address to fill in + * @ret len Length of setting, or negative error + */ +int fetch_ipv4_setting ( struct settings *settings, + const struct setting *setting, + struct in_addr *inp ) { + + return fetch_ipv4_array_setting ( settings, setting, inp, 1 ); +} + +/** + * Fetch value of IPv6 address setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v inp IPv6 addresses to fill in + * @v count Maximum number of IPv6 addresses + * @ret len Length of setting, or negative error + */ +int fetch_ipv6_array_setting ( struct settings *settings, + const struct setting *setting, + struct in6_addr *inp, unsigned int count ) { + int len; + + len = fetch_raw_setting ( settings, setting, inp, + ( sizeof ( *inp ) * count ) ); + if ( len < 0 ) + return len; + if ( ( len % sizeof ( *inp ) ) != 0 ) + return -ERANGE; + return len; +} + +/** + * Fetch value of IPv6 address setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v inp IPv6 address to fill in + * @ret len Length of setting, or negative error + */ +int fetch_ipv6_setting ( struct settings *settings, + const struct setting *setting, + struct in6_addr *inp ) { + + return fetch_ipv6_array_setting ( settings, setting, inp, 1 ); +} + +/** + * Extract numeric value of setting + * + * @v is_signed Treat value as a signed integer + * @v raw Raw setting data + * @v len Length of raw setting data + * @ret value Numeric value + * @ret len Length of setting, or negative error + */ +static int numeric_setting_value ( int is_signed, const void *raw, size_t len, + unsigned long *value ) { + const uint8_t *unsigned_bytes = raw; + const int8_t *signed_bytes = raw; + int is_negative; + unsigned int i; + uint8_t pad; + uint8_t byte; + + /* Convert to host-ordered longs */ + is_negative = ( len && ( signed_bytes[0] < 0 ) ); + *value = ( ( is_signed && is_negative ) ? -1L : 0 ); + pad = *value; + for ( i = 0 ; i < len ; i++ ) { + byte = unsigned_bytes[i]; + *value = ( ( *value << 8 ) | byte ); + if ( ( ( i + sizeof ( *value ) ) < len ) && ( byte != pad ) ) + return -ERANGE; + } + + return len; +} + +/** + * Fetch value of numeric setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v value Integer value to fill in + * @ret len Length of setting, or negative error + */ +int fetch_numeric_setting ( struct settings *settings, + const struct setting *setting, + unsigned long *value, int is_signed ) { + unsigned long tmp; + int len; + + /* Avoid returning uninitialised data on error */ + *value = 0; + + /* Fetch raw (network-ordered, variable-length) setting */ + len = fetch_raw_setting ( settings, setting, &tmp, sizeof ( tmp ) ); + if ( len < 0 ) + return len; + + /* Extract numeric value */ + return numeric_setting_value ( is_signed, &tmp, len, value ); +} + +/** + * Fetch value of signed integer setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v value Integer value to fill in + * @ret len Length of setting, or negative error + */ +int fetch_int_setting ( struct settings *settings, + const struct setting *setting, + long *value ) { + + return fetch_numeric_setting ( settings, setting, + ( ( unsigned long * ) value ), 1 ); +} + +/** + * Fetch value of unsigned integer setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v value Integer value to fill in + * @ret len Length of setting, or negative error + */ +int fetch_uint_setting ( struct settings *settings, + const struct setting *setting, + unsigned long *value ) { + + return fetch_numeric_setting ( settings, setting, value, 0 ); +} + +/** + * Fetch value of signed integer setting, or zero + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @ret value Setting value, or zero + */ +long fetch_intz_setting ( struct settings *settings, + const struct setting *setting ) { + unsigned long value; + + fetch_numeric_setting ( settings, setting, &value, 1 ); + return value; +} + +/** + * Fetch value of unsigned integer setting, or zero + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @ret value Setting value, or zero + */ +unsigned long fetch_uintz_setting ( struct settings *settings, + const struct setting *setting ) { + unsigned long value; + + fetch_numeric_setting ( settings, setting, &value, 0 ); + return value; +} + +/** + * Fetch value of UUID setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v uuid UUID to fill in + * @ret len Length of setting, or negative error + */ +int fetch_uuid_setting ( struct settings *settings, + const struct setting *setting, + union uuid *uuid ) { + int len; + + len = fetch_raw_setting ( settings, setting, uuid, sizeof ( *uuid ) ); + if ( len < 0 ) + return len; + if ( len != sizeof ( *uuid ) ) + return -ERANGE; + return len; +} + +/** + * Clear settings block + * + * @v settings Settings block + */ +void clear_settings ( struct settings *settings ) { + + /* Find target settings block */ + settings = settings_target ( settings ); + + /* Clear settings, if applicable */ + if ( settings->op->clear ) + settings->op->clear ( settings ); +} + +/** + * Compare two settings + * + * @v a Setting to compare + * @v b Setting to compare + * @ret 0 Settings are the same + * @ret non-zero Settings are not the same + */ +int setting_cmp ( const struct setting *a, const struct setting *b ) { + + /* If the settings have tags, compare them */ + if ( a->tag && ( a->tag == b->tag ) && ( a->scope == b->scope ) ) + return 0; + + /* Otherwise, if the settings have names, compare them */ + if ( a->name && b->name && a->name[0] ) + return strcmp ( a->name, b->name ); + + /* Otherwise, return a non-match */ + return ( ! 0 ); +} + +/****************************************************************************** + * + * Formatted setting routines + * + ****************************************************************************** + */ + +/** + * Format setting value as a string + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +int setting_format ( const struct setting_type *type, const void *raw, + size_t raw_len, char *buf, size_t len ) { + + /* Sanity check */ + if ( ! type->format ) + return -ENOTSUP; + + return type->format ( type, raw, raw_len, buf, len ); +} + +/** + * Parse formatted string to setting value + * + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @ret len Length of raw value, or negative error + */ +int setting_parse ( const struct setting_type *type, const char *value, + void *buf, size_t len ) { + + /* Sanity check */ + if ( ! type->parse ) + return -ENOTSUP; + + return type->parse ( type, value, buf, len ); +} + +/** + * Convert setting value to number + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @ret value Numeric value + * @ret rc Return status code + */ +int setting_numerate ( const struct setting_type *type, const void *raw, + size_t raw_len, unsigned long *value ) { + + /* Sanity check */ + if ( ! type->numerate ) + return -ENOTSUP; + + return type->numerate ( type, raw, raw_len, value ); +} + +/** + * Convert number to setting value + * + * @v type Setting type + * @v value Numeric value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @ret len Length of raw value, or negative error + */ +int setting_denumerate ( const struct setting_type *type, unsigned long value, + void *buf, size_t len ) { + + /* Sanity check */ + if ( ! type->denumerate ) + return -ENOTSUP; + + return type->denumerate ( type, value, buf, len ); +} + +/** + * Fetch formatted value of setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v origin Origin of setting to fill in, or NULL + * @v fetched Fetched setting to fill in, or NULL + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +int fetchf_setting ( struct settings *settings, const struct setting *setting, + struct settings **origin, struct setting *fetched, + char *buf, size_t len ) { + struct setting tmp_fetched; + void *raw; + int raw_len; + int ret; + + /* Use local buffers if necessary */ + if ( ! fetched ) + fetched = &tmp_fetched; + + /* Fetch raw value */ + raw_len = fetch_setting_copy ( settings, setting, origin, fetched, + &raw ); + if ( raw_len < 0 ) { + ret = raw_len; + goto err_fetch_copy; + } + + /* Sanity check */ + assert ( fetched->type != NULL ); + + /* Format setting */ + if ( ( ret = setting_format ( fetched->type, raw, raw_len, buf, + len ) ) < 0 ) + goto err_format; + + err_format: + free ( raw ); + err_fetch_copy: + return ret; +} + +/** + * Fetch copy of formatted value of setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v origin Origin of setting to fill in, or NULL + * @v fetched Fetched setting to fill in, or NULL + * @v value Buffer to allocate and fill with formatted value + * @ret len Length of formatted value, or negative error + * + * The caller is responsible for eventually freeing the allocated + * buffer. + */ +int fetchf_setting_copy ( struct settings *settings, + const struct setting *setting, + struct settings **origin, struct setting *fetched, + char **value ) { + struct settings *tmp_origin; + struct setting tmp_fetched; + int len; + int check_len; + + /* Use local buffers if necessary */ + if ( ! origin ) + origin = &tmp_origin; + if ( ! fetched ) + fetched = &tmp_fetched; + + /* Avoid returning uninitialised data on error */ + *value = NULL; + + /* Check existence, and fetch formatted value length */ + len = fetchf_setting ( settings, setting, origin, fetched, NULL, 0 ); + if ( len < 0 ) + return len; + + /* Allocate buffer */ + *value = zalloc ( len + 1 /* NUL */ ); + if ( ! *value ) + return -ENOMEM; + + /* Fetch formatted value */ + check_len = fetchf_setting ( *origin, fetched, NULL, NULL, *value, + ( len + 1 /* NUL */ ) ); + assert ( check_len == len ); + return len; +} + +/** + * Store formatted value of setting + * + * @v settings Settings block + * @v setting Setting to store + * @v value Formatted setting data, or NULL + * @ret rc Return status code + */ +int storef_setting ( struct settings *settings, const struct setting *setting, + const char *value ) { + void *raw; + int raw_len; + int check_len; + int rc; + + /* NULL value or empty string implies deletion */ + if ( ( ! value ) || ( ! value[0] ) ) + return delete_setting ( settings, setting ); + + /* Sanity check */ + assert ( setting->type != NULL ); + + /* Get raw value length */ + raw_len = setting_parse ( setting->type, value, NULL, 0 ); + if ( raw_len < 0 ) { + rc = raw_len; + goto err_raw_len; + } + + /* Allocate buffer for raw value */ + raw = malloc ( raw_len ); + if ( ! raw ) { + rc = -ENOMEM; + goto err_alloc_raw; + } + + /* Parse formatted value */ + check_len = setting_parse ( setting->type, value, raw, raw_len ); + assert ( check_len == raw_len ); + + /* Store raw value */ + if ( ( rc = store_setting ( settings, setting, raw, raw_len ) ) != 0 ) + goto err_store; + + err_store: + free ( raw ); + err_alloc_raw: + err_raw_len: + return rc; +} + +/** + * Fetch numeric value of setting + * + * @v settings Settings block, or NULL to search all blocks + * @v setting Setting to fetch + * @v origin Origin of setting to fill in, or NULL + * @v fetched Fetched setting to fill in, or NULL + * @v value Numeric value to fill in + * @ret rc Return status code + */ +int fetchn_setting ( struct settings *settings, const struct setting *setting, + struct settings **origin, struct setting *fetched, + unsigned long *value ) { + struct setting tmp_fetched; + void *raw; + int raw_len; + int rc; + + /* Use local buffers if necessary */ + if ( ! fetched ) + fetched = &tmp_fetched; + + /* Fetch raw value */ + raw_len = fetch_setting_copy ( settings, setting, origin, fetched, + &raw ); + if ( raw_len < 0 ) { + rc = raw_len; + goto err_fetch_copy; + } + + /* Sanity check */ + assert ( fetched->type != NULL ); + + /* Numerate setting */ + if ( ( rc = setting_numerate ( fetched->type, raw, raw_len, + value ) ) < 0 ) + goto err_numerate; + + err_numerate: + free ( raw ); + err_fetch_copy: + return rc; +} + +/** + * Store numeric value of setting + * + * @v settings Settings block + * @v setting Setting + * @v value Numeric value + * @ret rc Return status code + */ +int storen_setting ( struct settings *settings, const struct setting *setting, + unsigned long value ) { + void *raw; + int raw_len; + int check_len; + int rc; + + /* Sanity check */ + assert ( setting->type != NULL ); + + /* Get raw value length */ + raw_len = setting_denumerate ( setting->type, value, NULL, 0 ); + if ( raw_len < 0 ) { + rc = raw_len; + goto err_raw_len; + } + + /* Allocate buffer for raw value */ + raw = malloc ( raw_len ); + if ( ! raw ) { + rc = -ENOMEM; + goto err_alloc_raw; + } + + /* Denumerate value */ + check_len = setting_denumerate ( setting->type, value, raw, raw_len ); + assert ( check_len == raw_len ); + + /* Store raw value */ + if ( ( rc = store_setting ( settings, setting, raw, raw_len ) ) != 0 ) + goto err_store; + + err_store: + free ( raw ); + err_alloc_raw: + err_raw_len: + return rc; +} + +/****************************************************************************** + * + * Named settings + * + ****************************************************************************** + */ + +/** + * Find predefined setting + * + * @v name Name + * @ret setting Setting, or NULL + */ +struct setting * find_setting ( const char *name ) { + struct setting *setting; + + for_each_table_entry ( setting, SETTINGS ) { + if ( strcmp ( name, setting->name ) == 0 ) + return setting; + } + return NULL; +} + +/** + * Parse setting name as tag number + * + * @v name Name + * @ret tag Tag number, or 0 if not a valid number + */ +static unsigned int parse_setting_tag ( const char *name ) { + char *tmp = ( ( char * ) name ); + unsigned int tag = 0; + + while ( 1 ) { + tag = ( ( tag << 8 ) | strtoul ( tmp, &tmp, 0 ) ); + if ( *tmp == 0 ) + return tag; + if ( *tmp != '.' ) + return 0; + tmp++; + } +} + +/** + * Find setting type + * + * @v name Name + * @ret type Setting type, or NULL + */ +static const struct setting_type * find_setting_type ( const char *name ) { + const struct setting_type *type; + + for_each_table_entry ( type, SETTING_TYPES ) { + if ( strcmp ( name, type->name ) == 0 ) + return type; + } + return NULL; +} + +/** + * Parse setting name + * + * @v name Name of setting + * @v get_child Function to find or create child settings block + * @v settings Settings block to fill in + * @v setting Setting to fill in + * @ret rc Return status code + * + * Interprets a name of the form + * "[settings_name/]tag_name[:type_name]" and fills in the appropriate + * fields. + * + * Note that on success, this function will have modified the original + * setting @c name. + */ +int parse_setting_name ( char *name, get_child_settings_t get_child, + struct settings **settings, struct setting *setting ) { + char *settings_name; + char *setting_name; + char *type_name; + struct setting *predefined; + int rc; + + /* Set defaults */ + *settings = &settings_root; + memset ( setting, 0, sizeof ( *setting ) ); + setting->name = ""; + + /* Split name into "[settings_name/]setting_name[:type_name]" */ + if ( ( setting_name = strchr ( name, '/' ) ) != NULL ) { + *(setting_name++) = 0; + settings_name = name; + } else { + setting_name = name; + settings_name = NULL; + } + if ( ( type_name = strchr ( setting_name, ':' ) ) != NULL ) + *(type_name++) = 0; + + /* Identify settings block, if specified */ + if ( settings_name ) { + *settings = parse_settings_name ( settings_name, get_child ); + if ( *settings == NULL ) { + DBG ( "Unrecognised settings block \"%s\" in \"%s\"\n", + settings_name, name ); + rc = -ENODEV; + goto err; + } + } + + /* Identify setting */ + setting->tag = parse_setting_tag ( setting_name ); + setting->scope = (*settings)->default_scope; + setting->name = setting_name; + for_each_table_entry ( predefined, SETTINGS ) { + /* Matches a predefined setting; use that setting */ + if ( setting_cmp ( predefined, setting ) == 0 ) { + memcpy ( setting, predefined, sizeof ( *setting ) ); + break; + } + } + + /* Identify setting type, if specified */ + if ( type_name ) { + setting->type = find_setting_type ( type_name ); + if ( setting->type == NULL ) { + DBG ( "Invalid setting type \"%s\" in \"%s\"\n", + type_name, name ); + rc = -ENOTSUP; + goto err; + } + } + + return 0; + + err: + /* Restore original name */ + if ( settings_name ) + *( setting_name - 1 ) = '/'; + if ( type_name ) + *( type_name - 1 ) = ':'; + return rc; +} + +/** + * Return full setting name + * + * @v settings Settings block, or NULL + * @v setting Setting + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of setting name, or negative error + */ +int setting_name ( struct settings *settings, const struct setting *setting, + char *buf, size_t len ) { + const char *name; + + settings = settings_target ( settings ); + name = settings_name ( settings ); + return snprintf ( buf, len, "%s%s%s:%s", name, ( name[0] ? "/" : "" ), + setting->name, setting->type->name ); +} + +/****************************************************************************** + * + * Setting types + * + ****************************************************************************** + */ + +/** + * Parse string setting value + * + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @ret len Length of raw value, or negative error + */ +static int parse_string_setting ( const struct setting_type *type __unused, + const char *value, void *buf, size_t len ) { + size_t raw_len = strlen ( value ); /* Exclude terminating NUL */ + + /* Copy string to buffer */ + if ( len > raw_len ) + len = raw_len; + memcpy ( buf, value, len ); + + return raw_len; +} + +/** + * Format string setting value + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_string_setting ( const struct setting_type *type __unused, + const void *raw, size_t raw_len, char *buf, + size_t len ) { + + /* Copy string to buffer, and terminate */ + memset ( buf, 0, len ); + if ( len > raw_len ) + len = raw_len; + memcpy ( buf, raw, len ); + + return raw_len; +} + +/** A string setting type */ +const struct setting_type setting_type_string __setting_type = { + .name = "string", + .parse = parse_string_setting, + .format = format_string_setting, +}; + +/** A URI-encoded string setting type + * + * This setting type is obsolete; the name ":uristring" is retained to + * avoid breaking existing scripts. + */ +const struct setting_type setting_type_uristring __setting_type = { + .name = "uristring", + .parse = parse_string_setting, + .format = format_string_setting, +}; + +/** + * Parse IPv4 address setting value (when IPv4 support is not present) + * + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @ret len Length of raw value, or negative error + */ +__weak int parse_ipv4_setting ( const struct setting_type *type __unused, + const char *value __unused, void *buf __unused, + size_t len __unused ) { + return -ENOTSUP; +} + +/** + * Format IPv4 address setting value (when IPv4 support is not present) + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +__weak int format_ipv4_setting ( const struct setting_type *type __unused, + const void *raw __unused, + size_t raw_len __unused, char *buf __unused, + size_t len __unused ) { + return -ENOTSUP; +} + +/** An IPv4 address setting type */ +const struct setting_type setting_type_ipv4 __setting_type = { + .name = "ipv4", + .parse = parse_ipv4_setting, + .format = format_ipv4_setting, +}; + +/** + * Parse IPv6 address setting value (when IPv6 support is not present) + * + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @ret len Length of raw value, or negative error + */ +__weak int parse_ipv6_setting ( const struct setting_type *type __unused, + const char *value __unused, void *buf __unused, + size_t len __unused ) { + return -ENOTSUP; +} + +/** + * Format IPv6 address setting value (when IPv6 support is not present) + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +__weak int format_ipv6_setting ( const struct setting_type *type __unused, + const void *raw __unused, + size_t raw_len __unused, char *buf __unused, + size_t len __unused ) { + return -ENOTSUP; +} + +/** An IPv6 address setting type */ +const struct setting_type setting_type_ipv6 __setting_type = { + .name = "ipv6", + .parse = parse_ipv6_setting, + .format = format_ipv6_setting, +}; + +/** IPv6 settings scope */ +const struct settings_scope ipv6_scope; + +/** + * Integer setting type indices + * + * These indexes are defined such that (1<<index) gives the width of + * the integer, in bytes. + */ +enum setting_type_int_index { + SETTING_TYPE_INT8 = 0, + SETTING_TYPE_INT16 = 1, + SETTING_TYPE_INT32 = 2, +}; + +/** + * Integer setting type names + * + * These names exist as a static array in order to allow the type's + * integer size and signedness to be determined from the type's name. + * Note that there are no separate entries for the signed integer + * types: the name pointers simply point to the second character of + * the relevant string. + */ +static const char setting_type_int_name[][8] = { + [SETTING_TYPE_INT8] = "uint8", + [SETTING_TYPE_INT16] = "uint16", + [SETTING_TYPE_INT32] = "uint32", +}; + +/** + * Get unsigned integer setting type name + * + * @v index Integer setting type index + * @ret name Setting type name + */ +#define SETTING_TYPE_UINT_NAME( index ) setting_type_int_name[index] + +/** + * Get signed integer setting type name + * + * @v index Integer setting type index + * @ret name Setting type name + */ +#define SETTING_TYPE_INT_NAME( index ) ( setting_type_int_name[index] + 1 ) + +/** + * Get integer setting type index + * + * @v type Setting type + * @ret index Integer setting type index + */ +static unsigned int setting_type_int_index ( const struct setting_type *type ) { + + return ( ( type->name - setting_type_int_name[0] ) / + sizeof ( setting_type_int_name[0] ) ); +} + +/** + * Get integer setting type width + * + * @v type Setting type + * @ret index Integer setting type width + */ +static unsigned int setting_type_int_width ( const struct setting_type *type ) { + + return ( 1 << setting_type_int_index ( type ) ); +} + +/** + * Get integer setting type signedness + * + * @v type Setting type + * @ret is_signed Integer setting type is signed + */ +static int setting_type_int_is_signed ( const struct setting_type *type ) { + return ( ( type->name - setting_type_int_name[0] ) & 1 ); +} + +/** + * Convert number to setting value + * + * @v type Setting type + * @v value Numeric value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @ret len Length of raw value, or negative error + */ +static int denumerate_int_setting ( const struct setting_type *type, + unsigned long value, void *buf, + size_t len ) { + unsigned int size = setting_type_int_width ( type ); + union { + uint32_t num; + uint8_t bytes[4]; + } u; + + u.num = htonl ( value ); + if ( len > size ) + len = size; + memcpy ( buf, &u.bytes[ sizeof ( u ) - size ], len ); + + return size; +} + +/** + * Convert setting value to number + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v value Numeric value to fill in + * @ret rc Return status code + */ +static int numerate_int_setting ( const struct setting_type *type, + const void *raw, size_t raw_len, + unsigned long *value ) { + int is_signed = setting_type_int_is_signed ( type ); + int check_len; + + /* Extract numeric value */ + check_len = numeric_setting_value ( is_signed, raw, raw_len, value ); + if ( check_len < 0 ) + return check_len; + assert ( check_len == ( int ) raw_len ); + + return 0; +} + +/** + * Parse integer setting value + * + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @ret len Length of raw value, or negative error + */ +static int parse_int_setting ( const struct setting_type *type, + const char *value, void *buf, size_t len ) { + char *endp; + unsigned long num_value; + + /* Parse value */ + num_value = strtoul ( value, &endp, 0 ); + if ( *endp ) + return -EINVAL; + + return type->denumerate ( type, num_value, buf, len ); +} + +/** + * Format signed integer setting value + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_int_setting ( const struct setting_type *type, + const void *raw, size_t raw_len, + char *buf, size_t len ) { + unsigned long value; + int ret; + + /* Extract numeric value */ + if ( ( ret = type->numerate ( type, raw, raw_len, &value ) ) < 0 ) + return ret; + + /* Format value */ + return snprintf ( buf, len, "%ld", value ); +} + +/** + * Format unsigned integer setting value + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_uint_setting ( const struct setting_type *type, + const void *raw, size_t raw_len, + char *buf, size_t len ) { + unsigned long value; + int ret; + + /* Extract numeric value */ + if ( ( ret = type->numerate ( type, raw, raw_len, &value ) ) < 0 ) + return ret; + + /* Format value */ + return snprintf ( buf, len, "%#lx", value ); +} + +/** + * Define a signed integer setting type + * + * @v index Integer setting type index + * @ret type Setting type + */ +#define SETTING_TYPE_INT( index ) { \ + .name = SETTING_TYPE_INT_NAME ( index ), \ + .parse = parse_int_setting, \ + .format = format_int_setting, \ + .denumerate = denumerate_int_setting, \ + .numerate = numerate_int_setting, \ +} + +/** + * Define an unsigned integer setting type + * + * @v index Integer setting type index + * @ret type Setting type + */ +#define SETTING_TYPE_UINT( index ) { \ + .name = SETTING_TYPE_UINT_NAME ( index ), \ + .parse = parse_int_setting, \ + .format = format_uint_setting, \ + .denumerate = denumerate_int_setting, \ + .numerate = numerate_int_setting, \ +} + +/** A signed 8-bit integer setting type */ +const struct setting_type setting_type_int8 __setting_type = + SETTING_TYPE_INT ( SETTING_TYPE_INT8 ); + +/** A signed 16-bit integer setting type */ +const struct setting_type setting_type_int16 __setting_type = + SETTING_TYPE_INT ( SETTING_TYPE_INT16 ); + +/** A signed 32-bit integer setting type */ +const struct setting_type setting_type_int32 __setting_type = + SETTING_TYPE_INT ( SETTING_TYPE_INT32 ); + +/** An unsigned 8-bit integer setting type */ +const struct setting_type setting_type_uint8 __setting_type = + SETTING_TYPE_UINT ( SETTING_TYPE_INT8 ); + +/** An unsigned 16-bit integer setting type */ +const struct setting_type setting_type_uint16 __setting_type = + SETTING_TYPE_UINT ( SETTING_TYPE_INT16 ); + +/** An unsigned 32-bit integer setting type */ +const struct setting_type setting_type_uint32 __setting_type = + SETTING_TYPE_UINT ( SETTING_TYPE_INT32 ); + +/** + * Format hex string setting value + * + * @v delimiter Byte delimiter + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_hex_setting ( const char *delimiter, const void *raw, + size_t raw_len, char *buf, size_t len ) { + const uint8_t *bytes = raw; + int used = 0; + unsigned int i; + + if ( len ) + buf[0] = 0; /* Ensure that a terminating NUL exists */ + for ( i = 0 ; i < raw_len ; i++ ) { + used += ssnprintf ( ( buf + used ), ( len - used ), + "%s%02x", ( used ? delimiter : "" ), + bytes[i] ); + } + return used; +} + +/** + * Parse hex string setting value (using colon delimiter) + * + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @v size Integer size, in bytes + * @ret len Length of raw value, or negative error + */ +static int parse_hex_setting ( const struct setting_type *type __unused, + const char *value, void *buf, size_t len ) { + return hex_decode ( value, ':', buf, len ); +} + +/** + * Format hex string setting value (using colon delimiter) + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_hex_colon_setting ( const struct setting_type *type __unused, + const void *raw, size_t raw_len, + char *buf, size_t len ) { + return format_hex_setting ( ":", raw, raw_len, buf, len ); +} + +/** + * Parse hex string setting value (using hyphen delimiter) + * + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @v size Integer size, in bytes + * @ret len Length of raw value, or negative error + */ +static int parse_hex_hyphen_setting ( const struct setting_type *type __unused, + const char *value, void *buf, + size_t len ) { + return hex_decode ( value, '-', buf, len ); +} + +/** + * Format hex string setting value (using hyphen delimiter) + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_hex_hyphen_setting ( const struct setting_type *type __unused, + const void *raw, size_t raw_len, + char *buf, size_t len ) { + return format_hex_setting ( "-", raw, raw_len, buf, len ); +} + +/** + * Parse hex string setting value (using no delimiter) + * + * @v type Setting type + * @v value Formatted setting value + * @v buf Buffer to contain raw value + * @v len Length of buffer + * @v size Integer size, in bytes + * @ret len Length of raw value, or negative error + */ +static int parse_hex_raw_setting ( const struct setting_type *type __unused, + const char *value, void *buf, size_t len ) { + return hex_decode ( value, 0, buf, len ); +} + +/** + * Format hex string setting value (using no delimiter) + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_hex_raw_setting ( const struct setting_type *type __unused, + const void *raw, size_t raw_len, + char *buf, size_t len ) { + return format_hex_setting ( "", raw, raw_len, buf, len ); +} + +/** A hex-string setting (colon-delimited) */ +const struct setting_type setting_type_hex __setting_type = { + .name = "hex", + .parse = parse_hex_setting, + .format = format_hex_colon_setting, +}; + +/** A hex-string setting (hyphen-delimited) */ +const struct setting_type setting_type_hexhyp __setting_type = { + .name = "hexhyp", + .parse = parse_hex_hyphen_setting, + .format = format_hex_hyphen_setting, +}; + +/** A hex-string setting (non-delimited) */ +const struct setting_type setting_type_hexraw __setting_type = { + .name = "hexraw", + .parse = parse_hex_raw_setting, + .format = format_hex_raw_setting, +}; + +/** + * Format UUID setting value + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_uuid_setting ( const struct setting_type *type __unused, + const void *raw, size_t raw_len, char *buf, + size_t len ) { + const union uuid *uuid = raw; + + /* Range check */ + if ( raw_len != sizeof ( *uuid ) ) + return -ERANGE; + + /* Format value */ + return snprintf ( buf, len, "%s", uuid_ntoa ( uuid ) ); +} + +/** UUID setting type */ +const struct setting_type setting_type_uuid __setting_type = { + .name = "uuid", + .format = format_uuid_setting, +}; + +/** + * Format PCI bus:dev.fn setting value + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_busdevfn_setting ( const struct setting_type *type __unused, + const void *raw, size_t raw_len, char *buf, + size_t len ) { + unsigned long busdevfn; + int check_len; + + /* Extract numeric value */ + check_len = numeric_setting_value ( 0, raw, raw_len, &busdevfn ); + if ( check_len < 0 ) + return check_len; + assert ( check_len == ( int ) raw_len ); + + /* Format value */ + return snprintf ( buf, len, "%02lx:%02lx.%lx", PCI_BUS ( busdevfn ), + PCI_SLOT ( busdevfn ), PCI_FUNC ( busdevfn ) ); +} + +/** PCI bus:dev.fn setting type */ +const struct setting_type setting_type_busdevfn __setting_type = { + .name = "busdevfn", + .format = format_busdevfn_setting, +}; + +/****************************************************************************** + * + * Setting expansion + * + ****************************************************************************** + */ + +/** + * Expand variables within string + * + * @v string String + * @ret expstr Expanded string + * + * The expanded string is allocated with malloc() and the caller must + * eventually free() it. + */ +char * expand_settings ( const char *string ) { + struct settings *settings; + struct setting setting; + char *expstr; + char *start; + char *end; + char *head; + char *name; + char *tail; + char *value; + char *tmp; + int new_len; + int rc; + + /* Obtain temporary modifiable copy of string */ + expstr = strdup ( string ); + if ( ! expstr ) + return NULL; + + /* Expand while expansions remain */ + while ( 1 ) { + + head = expstr; + + /* Locate setting to be expanded */ + start = NULL; + end = NULL; + for ( tmp = expstr ; *tmp ; tmp++ ) { + if ( ( tmp[0] == '$' ) && ( tmp[1] == '{' ) ) + start = tmp; + if ( start && ( tmp[0] == '}' ) ) { + end = tmp; + break; + } + } + if ( ! end ) + break; + *start = '\0'; + name = ( start + 2 ); + *end = '\0'; + tail = ( end + 1 ); + + /* Expand setting */ + if ( ( rc = parse_setting_name ( name, find_child_settings, + &settings, + &setting ) ) != 0 ) { + /* Treat invalid setting names as empty */ + value = NULL; + } else { + /* Fetch and format setting value. Ignore + * errors; treat non-existent settings as empty. + */ + fetchf_setting_copy ( settings, &setting, NULL, NULL, + &value ); + } + + /* Construct expanded string and discard old string */ + tmp = expstr; + new_len = asprintf ( &expstr, "%s%s%s", + head, ( value ? value : "" ), tail ); + free ( value ); + free ( tmp ); + if ( new_len < 0 ) + return NULL; + } + + return expstr; +} + +/****************************************************************************** + * + * Settings + * + ****************************************************************************** + */ + +/** Hostname setting */ +const struct setting hostname_setting __setting ( SETTING_HOST, hostname ) = { + .name = "hostname", + .description = "Host name", + .tag = DHCP_HOST_NAME, + .type = &setting_type_string, +}; + +/** Domain name setting */ +const struct setting domain_setting __setting ( SETTING_IP_EXTRA, domain ) = { + .name = "domain", + .description = "DNS domain", + .tag = DHCP_DOMAIN_NAME, + .type = &setting_type_string, +}; + +/** TFTP server setting */ +const struct setting next_server_setting __setting ( SETTING_BOOT,next-server)={ + .name = "next-server", + .description = "TFTP server", + .tag = DHCP_EB_SIADDR, + .type = &setting_type_ipv4, +}; + +/** Filename setting */ +const struct setting filename_setting __setting ( SETTING_BOOT, filename ) = { + .name = "filename", + .description = "Boot filename", + .tag = DHCP_BOOTFILE_NAME, + .type = &setting_type_string, +}; + +/** Root path setting */ +const struct setting root_path_setting __setting ( SETTING_SANBOOT, root-path)={ + .name = "root-path", + .description = "SAN root path", + .tag = DHCP_ROOT_PATH, + .type = &setting_type_string, +}; + +/** Username setting */ +const struct setting username_setting __setting ( SETTING_AUTH, username ) = { + .name = "username", + .description = "User name", + .tag = DHCP_EB_USERNAME, + .type = &setting_type_string, +}; + +/** Password setting */ +const struct setting password_setting __setting ( SETTING_AUTH, password ) = { + .name = "password", + .description = "Password", + .tag = DHCP_EB_PASSWORD, + .type = &setting_type_string, +}; + +/** Priority setting */ +const struct setting priority_setting __setting ( SETTING_MISC, priority ) = { + .name = "priority", + .description = "Settings priority", + .tag = DHCP_EB_PRIORITY, + .type = &setting_type_int8, +}; + +/** DHCP user class setting */ +const struct setting user_class_setting __setting ( SETTING_HOST_EXTRA, + user-class ) = { + .name = "user-class", + .description = "DHCP user class", + .tag = DHCP_USER_CLASS_ID, + .type = &setting_type_string, +}; + +/****************************************************************************** + * + * Built-in settings block + * + ****************************************************************************** + */ + +/** Built-in setting scope */ +const struct settings_scope builtin_scope; + +/** + * Fetch error number setting + * + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int errno_fetch ( void *data, size_t len ) { + uint32_t content; + + /* Return current error */ + content = htonl ( errno ); + if ( len > sizeof ( content ) ) + len = sizeof ( content ); + memcpy ( data, &content, len ); + return sizeof ( content ); +} + +/** Error number setting */ +const struct setting errno_setting __setting ( SETTING_MISC, errno ) = { + .name = "errno", + .description = "Last error", + .type = &setting_type_uint32, + .scope = &builtin_scope, +}; + +/** Error number built-in setting */ +struct builtin_setting errno_builtin_setting __builtin_setting = { + .setting = &errno_setting, + .fetch = errno_fetch, +}; + +/** + * Fetch build architecture setting + * + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int buildarch_fetch ( void *data, size_t len ) { + static const char buildarch[] = _S2 ( ARCH ); + + strncpy ( data, buildarch, len ); + return ( sizeof ( buildarch ) - 1 /* NUL */ ); +} + +/** Build architecture setting */ +const struct setting buildarch_setting __setting ( SETTING_MISC, buildarch ) = { + .name = "buildarch", + .description = "Build architecture", + .type = &setting_type_string, + .scope = &builtin_scope, +}; + +/** Build architecture built-in setting */ +struct builtin_setting buildarch_builtin_setting __builtin_setting = { + .setting = &buildarch_setting, + .fetch = buildarch_fetch, +}; + +/** + * Fetch platform setting + * + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int platform_fetch ( void *data, size_t len ) { + static const char platform[] = _S2 ( PLATFORM ); + + strncpy ( data, platform, len ); + return ( sizeof ( platform ) - 1 /* NUL */ ); +} + +/** Platform setting */ +const struct setting platform_setting __setting ( SETTING_MISC, platform ) = { + .name = "platform", + .description = "Platform", + .type = &setting_type_string, + .scope = &builtin_scope, +}; + +/** Platform built-in setting */ +struct builtin_setting platform_builtin_setting __builtin_setting = { + .setting = &platform_setting, + .fetch = platform_fetch, +}; + +/** + * Fetch version setting + * + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int version_fetch ( void *data, size_t len ) { + strncpy ( data, product_version, len ); + return ( strlen ( product_version ) ); +} + +/** Version setting */ +const struct setting version_setting __setting ( SETTING_MISC, version ) = { + .name = "version", + .description = "Version", + .type = &setting_type_string, + .scope = &builtin_scope, +}; + +/** Version built-in setting */ +struct builtin_setting version_builtin_setting __builtin_setting = { + .setting = &version_setting, + .fetch = version_fetch, +}; + +/** + * Fetch built-in setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int builtin_fetch ( struct settings *settings __unused, + struct setting *setting, + void *data, size_t len ) { + struct builtin_setting *builtin; + + for_each_table_entry ( builtin, BUILTIN_SETTINGS ) { + if ( setting_cmp ( setting, builtin->setting ) == 0 ) + return builtin->fetch ( data, len ); + } + return -ENOENT; +} + +/** + * Check applicability of built-in setting + * + * @v settings Settings block + * @v setting Setting + * @ret applies Setting applies within this settings block + */ +static int builtin_applies ( struct settings *settings __unused, + const struct setting *setting ) { + + return ( setting->scope == &builtin_scope ); +} + +/** Built-in settings operations */ +static struct settings_operations builtin_settings_operations = { + .applies = builtin_applies, + .fetch = builtin_fetch, +}; + +/** Built-in settings */ +static struct settings builtin_settings = { + .refcnt = NULL, + .siblings = LIST_HEAD_INIT ( builtin_settings.siblings ), + .children = LIST_HEAD_INIT ( builtin_settings.children ), + .op = &builtin_settings_operations, +}; + +/** Initialise built-in settings */ +static void builtin_init ( void ) { + int rc; + + if ( ( rc = register_settings ( &builtin_settings, NULL, + "builtin" ) ) != 0 ) { + DBG ( "Could not register built-in settings: %s\n", + strerror ( rc ) ); + return; + } +} + +/** Built-in settings initialiser */ +struct init_fn builtin_init_fn __init_fn ( INIT_NORMAL ) = { + .initialise = builtin_init, +}; |