diff options
Diffstat (limited to 'gpxe/src/core/settings.c')
-rw-r--r-- | gpxe/src/core/settings.c | 500 |
1 files changed, 373 insertions, 127 deletions
diff --git a/gpxe/src/core/settings.c b/gpxe/src/core/settings.c index f34eb664..7d83101c 100644 --- a/gpxe/src/core/settings.c +++ b/gpxe/src/core/settings.c @@ -16,6 +16,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +FILE_LICENCE ( GPL2_OR_LATER ); + #include <stdint.h> #include <stdlib.h> #include <stdio.h> @@ -37,85 +39,326 @@ * */ -/** Registered settings */ -static struct setting settings[0] - __table_start ( struct setting, settings ); -static struct setting settings_end[0] - __table_end ( struct setting, settings ); +/****************************************************************************** + * + * Generic settings blocks + * + ****************************************************************************** + */ -/** Registered setting types */ -static struct setting_type setting_types[0] - __table_start ( struct setting_type, setting_types ); -static struct setting_type setting_types_end[0] - __table_end ( struct setting_type, setting_types ); +/** + * 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; +}; -/** Registered settings applicators */ -static struct settings_applicator settings_applicators[0] - __table_start ( struct settings_applicator, settings_applicators ); -static struct settings_applicator settings_applicators_end[0] - __table_end ( struct settings_applicator, settings_applicators ); +/** + * 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 * - * Registered settings blocks + * @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, + 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 simple setting + * Store value of generic setting * - * @v options DHCP option block + * @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 simple_settings_store ( struct settings *settings, struct setting *setting, - const void *data, size_t len ) { - struct simple_settings *simple = - container_of ( settings, struct simple_settings, settings ); - return dhcpopt_extensible_store ( &simple->dhcpopts, setting->tag, - data, len ); +int generic_settings_store ( struct settings *settings, + 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 simple setting + * Fetch value of generic setting * - * @v options DHCP option block + * @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 simple_settings_fetch ( struct settings *settings, struct setting *setting, - void *data, size_t len ) { - struct simple_settings *simple = - container_of ( settings, struct simple_settings, settings ); - return dhcpopt_fetch ( &simple->dhcpopts, setting->tag, data, len ); +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 ); + 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 ) ); } -/** Simple settings operations */ -struct settings_operations simple_settings_operations = { - .store = simple_settings_store, - .fetch = simple_settings_fetch, +/** Generic settings operations */ +struct settings_operations generic_settings_operations = { + .store = generic_settings_store, + .fetch = generic_settings_fetch, + .clear = generic_settings_clear, }; -/** Root simple settings block */ -struct simple_settings simple_settings_root = { +/****************************************************************************** + * + * Registered settings blocks + * + ****************************************************************************** + */ + +/** Root generic settings block */ +struct generic_settings generic_settings_root = { .settings = { .refcnt = NULL, .name = "", .siblings = - LIST_HEAD_INIT ( simple_settings_root.settings.siblings ), + LIST_HEAD_INIT ( generic_settings_root.settings.siblings ), .children = - LIST_HEAD_INIT ( simple_settings_root.settings.children ), - .op = &simple_settings_operations, + 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 simple_settings_root.settings +#define settings_root generic_settings_root.settings + +/** + * Find child named settings block + * + * @v parent Parent settings block + * @v name Name within this parent + * @ret settings Settings block, or NULL + */ +static struct settings * find_child_settings ( struct settings *parent, + const char *name ) { + struct settings *settings; + + /* 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; + } + + return NULL; +} + +/** + * Find or create child named settings block + * + * @v parent Parent settings block + * @v name Name within this parent + * @ret settings Settings block, or NULL + */ +static struct settings * autovivify_child_settings ( struct settings *parent, + const char *name ) { + struct { + struct generic_settings generic; + char name[ strlen ( name ) + 1 /* NUL */ ]; + } *new_child; + struct settings *settings; + + /* 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 ) ); + generic_settings_init ( &new_child->generic, NULL, new_child->name ); + settings = &new_child->generic.settings; + register_settings ( settings, parent ); + return settings; +} + +/** + * Return settings block name (for debug only) + * + * @v settings Settings block + * @ret name Settings block name + */ +static const char * settings_name ( struct settings *settings ) { + static char buf[64]; + char tmp[ sizeof ( buf ) ]; + int count; + + for ( count = 0 ; settings ; settings = settings->parent ) { + memcpy ( tmp, buf, sizeof ( tmp ) ); + snprintf ( buf, sizeof ( buf ), "%s%c%s", settings->name, + ( count++ ? '.' : '\0' ), tmp ); + } + return ( buf + 1 ); +} + +/** + * 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, + struct settings * ( * get_child ) ( struct settings *, + const char * ) ) { + 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 ) { + struct net_device *netdev; + + subname = remainder; + remainder = strchr ( subname, '.' ); + if ( remainder ) + *(remainder++) = '\0'; + + /* Special case "netX" root settings block */ + if ( ( subname == name_copy ) && ! strcmp ( subname, "netX" ) && + ( ( netdev = last_opened_netdev() ) != NULL ) ) + settings = get_child ( settings, netdev->name ); + else + settings = get_child ( settings, subname ); + + if ( ! settings ) + break; + } + + return settings; +} + +/** + * Find named 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 @@ -127,8 +370,7 @@ static int apply_settings ( void ) { int rc; /* Call all settings applicators */ - for ( applicator = settings_applicators ; - applicator < settings_applicators_end ; applicator++ ) { + 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 ) ); @@ -199,7 +441,8 @@ int register_settings ( struct settings *settings, struct settings *parent ) { ref_get ( parent->refcnt ); settings->parent = parent; list_add_tail ( &settings->siblings, &parent->children ); - DBGC ( settings, "Settings %p registered\n", settings ); + DBGC ( settings, "Settings %p (\"%s\") registered\n", + settings, settings_name ( settings ) ); /* Fix up settings priority */ reprioritise_settings ( settings ); @@ -217,63 +460,19 @@ int register_settings ( struct settings *settings, struct settings *parent ) { */ void unregister_settings ( struct settings *settings ) { + DBGC ( settings, "Settings %p (\"%s\") unregistered\n", + settings, settings_name ( settings ) ); + /* Remove from list of settings */ ref_put ( settings->refcnt ); ref_put ( settings->parent->refcnt ); settings->parent = NULL; list_del ( &settings->siblings ); - DBGC ( settings, "Settings %p unregistered\n", settings ); /* Apply potentially-updated settings */ apply_settings(); } -/** - * Find child named 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; - size_t len; - - /* NULL parent => add to settings root */ - if ( parent == NULL ) - parent = &settings_root; - - /* Look for a child whose name matches the initial component */ - list_for_each_entry ( settings, &parent->children, siblings ) { - len = strlen ( settings->name ); - if ( strncmp ( name, settings->name, len ) != 0 ) - continue; - if ( name[len] == 0 ) - return settings; - if ( name[len] == '.' ) - return find_child_settings ( settings, - ( name + len + 1 ) ); - } - - return NULL; -} - -/** - * Find named settings block - * - * @v name Name - * @ret settings Settings block, or NULL - */ -struct settings * find_settings ( const char *name ) { - - /* If name is empty, use the root */ - if ( ! *name ) - return &settings_root; - - return find_child_settings ( &settings_root, name ); -} - /****************************************************************************** * * Core settings routines @@ -298,6 +497,10 @@ int store_setting ( struct settings *settings, struct setting *setting, if ( ! settings ) settings = &settings_root; + /* Sanity check */ + if ( ! settings->op->store ) + return -ENOTSUP; + /* Store setting */ if ( ( rc = settings->op->store ( settings, setting, data, len ) ) != 0 ) @@ -345,6 +548,10 @@ int fetch_setting ( struct settings *settings, struct setting *setting, if ( ! settings ) settings = &settings_root; + /* Sanity check */ + if ( ! settings->op->fetch ) + return -ENOTSUP; + /* Try this block first */ if ( ( ret = settings->op->fetch ( settings, setting, data, len ) ) >= 0 ) @@ -411,7 +618,7 @@ int fetch_string_setting_copy ( struct settings *settings, struct setting *setting, char **data ) { int len; - int check_len; + int check_len = 0; len = fetch_setting_len ( settings, setting ); if ( len < 0 ) @@ -421,7 +628,8 @@ int fetch_string_setting_copy ( struct settings *settings, if ( ! *data ) return -ENOMEM; - fetch_string_setting ( settings, setting, *data, ( len + 1 ) ); + check_len = fetch_string_setting ( settings, setting, *data, + ( len + 1 ) ); assert ( check_len == len ); return len; } @@ -504,7 +712,8 @@ int fetch_uint_setting ( struct settings *settings, struct setting *setting, return len; /* Mask off sign-extended bits */ - *value = ( svalue & ( -1UL >> ( sizeof ( long ) - len ) ) ); + assert ( len <= ( int ) sizeof ( long ) ); + *value = ( svalue & ( -1UL >> ( 8 * ( sizeof ( long ) - len ) ) ) ); return len; } @@ -559,6 +768,16 @@ int fetch_uuid_setting ( struct settings *settings, struct setting *setting, } /** + * Clear settings block + * + * @v settings Settings block + */ +void clear_settings ( struct settings *settings ) { + if ( settings->op->clear ) + settings->op->clear ( settings ); +} + +/** * Compare two settings * * @v a Setting to compare @@ -572,8 +791,12 @@ int setting_cmp ( struct setting *a, struct setting *b ) { if ( a->tag && ( a->tag == b->tag ) ) return 0; - /* Otherwise, compare the names */ - return strcmp ( a->name, b->name ); + /* 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 ); } /****************************************************************************** @@ -614,7 +837,7 @@ int storef_setting ( struct settings *settings, struct setting *setting, static struct setting * find_setting ( const char *name ) { struct setting *setting; - for ( setting = settings ; setting < settings_end ; setting++ ) { + for_each_table_entry ( setting, SETTINGS ) { if ( strcmp ( name, setting->name ) == 0 ) return setting; } @@ -622,6 +845,26 @@ static struct setting * find_setting ( const char *name ) { } /** + * 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 @@ -630,7 +873,7 @@ static struct setting * find_setting ( const char *name ) { static struct setting_type * find_setting_type ( const char *name ) { struct setting_type *type; - for ( type = setting_types ; type < setting_types_end ; type++ ) { + for_each_table_entry ( type, SETTING_TYPES ) { if ( strcmp ( name, type->name ) == 0 ) return type; } @@ -641,30 +884,38 @@ static struct setting_type * find_setting_type ( const char *name ) { * 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 + * @v tmp_name Buffer for copy of setting name * @ret rc Return status code * * Interprets a name of the form * "[settings_name/]tag_name[:type_name]" and fills in the appropriate * fields. + * + * The @c tmp_name buffer must be large enough to hold a copy of the + * setting name. */ -static int parse_setting_name ( const char *name, struct settings **settings, - struct setting *setting ) { - char tmp_name[ strlen ( name ) + 1 ]; +static int +parse_setting_name ( const char *name, + struct settings * ( * get_child ) ( struct settings *, + const char * ), + struct settings **settings, struct setting *setting, + char *tmp_name ) { char *settings_name; char *setting_name; char *type_name; struct setting *named_setting; - char *tmp; /* Set defaults */ *settings = &settings_root; memset ( setting, 0, sizeof ( *setting ) ); - setting->type = &setting_type_hex; + setting->name = ""; + setting->type = &setting_type_string; /* Split name into "[settings_name/]setting_name[:type_name]" */ - memcpy ( tmp_name, name, sizeof ( tmp_name ) ); + strcpy ( tmp_name, name ); if ( ( setting_name = strchr ( tmp_name, '/' ) ) != NULL ) { *(setting_name++) = 0; settings_name = tmp_name; @@ -677,7 +928,7 @@ static int parse_setting_name ( const char *name, struct settings **settings, /* Identify settings block, if specified */ if ( settings_name ) { - *settings = find_settings ( settings_name ); + *settings = parse_settings_name ( settings_name, get_child ); if ( *settings == NULL ) { DBG ( "Unrecognised settings block \"%s\" in \"%s\"\n", settings_name, name ); @@ -685,25 +936,16 @@ static int parse_setting_name ( const char *name, struct settings **settings, } } - /* Identify tag number */ + /* Identify setting */ if ( ( named_setting = find_setting ( setting_name ) ) != NULL ) { + /* Matches a defined named setting; use that setting */ memcpy ( setting, named_setting, sizeof ( *setting ) ); - } else { - /* Unrecognised name: try to interpret as a tag number */ - tmp = setting_name; - while ( 1 ) { - setting->tag = ( ( setting->tag << 8 ) | - strtoul ( tmp, &tmp, 0 ) ); - if ( *tmp == 0 ) - break; - if ( *tmp != '.' ) { - DBG ( "Invalid setting \"%s\" in \"%s\"\n", - setting_name, name ); - return -ENOENT; - } - tmp++; - } + } else if ( ( setting->tag = parse_setting_tag ( setting_name ) ) !=0){ + /* Is a valid numeric tag; use the tag */ setting->tag |= (*settings)->tag_magic; + } else { + /* Use the arbitrary name */ + setting->name = setting_name; } /* Identify setting type, if specified */ @@ -729,9 +971,11 @@ static int parse_setting_name ( const char *name, struct settings **settings, int storef_named_setting ( const char *name, const char *value ) { struct settings *settings; struct setting setting; + char tmp_name[ strlen ( name ) + 1 ]; int rc; - if ( ( rc = parse_setting_name ( name, &settings, &setting ) ) != 0 ) + if ( ( rc = parse_setting_name ( name, autovivify_child_settings, + &settings, &setting, tmp_name )) != 0) return rc; return storef_setting ( settings, &setting, value ); } @@ -747,9 +991,11 @@ int storef_named_setting ( const char *name, const char *value ) { int fetchf_named_setting ( const char *name, char *buf, size_t len ) { struct settings *settings; struct setting setting; + char tmp_name[ strlen ( name ) + 1 ]; int rc; - if ( ( rc = parse_setting_name ( name, &settings, &setting ) ) != 0 ) + if ( ( rc = parse_setting_name ( name, find_child_settings, + &settings, &setting, tmp_name )) != 0) return rc; return fetchf_setting ( settings, &setting, buf, len ); } @@ -839,7 +1085,7 @@ static int fetchf_uristring ( struct settings *settings, fetch_string_setting ( settings, setting, raw_buf, sizeof ( raw_buf ) ); - return uri_encode ( raw_buf, buf, len ); + return uri_encode ( raw_buf, buf, len, URI_FRAGMENT ); } } @@ -1180,7 +1426,7 @@ struct setting filename_setting __setting = { /** Root path setting */ struct setting root_path_setting __setting = { .name = "root-path", - .description = "NFS/iSCSI root path", + .description = "iSCSI root path", .tag = DHCP_ROOT_PATH, .type = &setting_type_string, }; |