[Settings] Introduce settings applicators.
[people/mcb30/gpxe.git] / src / net / dhcpopts.c
index 4f67f84..75a9f2a 100644 (file)
  */
 
 #include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
 #include <byteswap.h>
 #include <errno.h>
 #include <string.h>
-#include <malloc.h>
 #include <assert.h>
-#include <vsprintf.h>
 #include <gpxe/list.h>
+#include <gpxe/in.h>
+#include <gpxe/uri.h>
 #include <gpxe/dhcp.h>
 
 /** @file
@@ -33,7 +35,7 @@
  */
 
 /** List of registered DHCP option blocks */
-static LIST_HEAD ( option_blocks );
+LIST_HEAD ( dhcp_option_blocks );
 
 /**
  * Obtain printable version of a DHCP option tag
@@ -85,6 +87,55 @@ unsigned long dhcp_num_option ( struct dhcp_option *option ) {
        return value;
 }
 
+/**
+ * Obtain value of an IPv4-address DHCP option
+ *
+ * @v option           DHCP option, or NULL
+ * @v inp              IPv4 address to fill in
+ *
+ * Parses the IPv4 address value from a DHCP option, if present.  It
+ * is permitted to call dhcp_ipv4_option() with @c option set to NULL;
+ * in this case the address will be set to 0.0.0.0.
+ */
+void dhcp_ipv4_option ( struct dhcp_option *option, struct in_addr *inp ) {
+       if ( option )
+               *inp = option->data.in;
+}
+
+/**
+ * Print DHCP string option value into buffer
+ *
+ * @v buf              Buffer into which to write the string
+ * @v size             Size of buffer
+ * @v option           DHCP option, or NULL
+ * @ret len            Length of formatted string
+ *
+ * DHCP option strings are stored without a NUL terminator.  This
+ * function provides a convenient way to extract these DHCP strings
+ * into standard C strings.  It is permitted to call dhcp_snprintf()
+ * with @c option set to NULL; in this case the buffer will be filled
+ * with an empty string.
+ *
+ * The usual snprintf() semantics apply with regard to buffer size,
+ * return value when the buffer is too small, etc.
+ */
+int dhcp_snprintf ( char *buf, size_t size, struct dhcp_option *option ) {
+       size_t len;
+       char *content = "";
+
+       if ( option ) {
+               /* Shrink buffer size so that it is only just large
+                * enough to contain the option data.  snprintf() will
+                * take care of everything else (inserting the NUL etc.)
+                */
+               len = ( option->len + 1 );
+               if ( len < size )
+                       size = len;
+               content = option->data.string;
+       }
+       return snprintf ( buf, size, "%s", content );
+}
+
 /**
  * Calculate length of a normal DHCP option
  *
@@ -202,7 +253,7 @@ struct dhcp_option * find_dhcp_option ( struct dhcp_option_block *options,
        if ( options ) {
                return find_dhcp_option_with_encap ( options, tag, NULL );
        } else {
-               list_for_each_entry ( options, &option_blocks, list ) {
+               list_for_each_entry ( options, &dhcp_option_blocks, list ) {
                        if ( ( option = find_dhcp_option ( options, tag ) ) )
                                return option;
                }
@@ -226,11 +277,15 @@ void register_dhcp_options ( struct dhcp_option_block *options ) {
              options, options->priority );
 
        /* Insert after any existing blocks which have a higher priority */
-       list_for_each_entry ( existing, &option_blocks, list ) {
+       list_for_each_entry ( existing, &dhcp_option_blocks, list ) {
                if ( options->priority > existing->priority )
                        break;
        }
+       dhcpopt_get ( options );
        list_add_tail ( &options->list, &existing->list );
+
+       /* Apply all registered DHCP options */
+       apply_global_dhcp_options();
 }
 
 /**
@@ -240,6 +295,7 @@ void register_dhcp_options ( struct dhcp_option_block *options ) {
  */
 void unregister_dhcp_options ( struct dhcp_option_block *options ) {
        list_del ( &options->list );
+       dhcpopt_put ( options );
 }
 
 /**
@@ -287,15 +343,6 @@ struct dhcp_option_block * alloc_dhcp_options ( size_t max_len ) {
        return options;
 }
 
-/**
- * Free DHCP options block
- *
- * @v options          DHCP option block
- */
-void free_dhcp_options ( struct dhcp_option_block *options ) {
-       free ( options );
-}
-
 /**
  * Resize a DHCP option
  *
@@ -359,25 +406,30 @@ struct dhcp_option * set_dhcp_option ( struct dhcp_option_block *options,
                                       const void *data, size_t len ) {
        static const uint8_t empty_encapsulator[] = { DHCP_END };
        struct dhcp_option *option;
-       void *insertion_point = options->data;
+       void *insertion_point;
        struct dhcp_option *encapsulator = NULL;
        unsigned int encap_tag = DHCP_ENCAPSULATOR ( tag );
        size_t old_len = 0;
        size_t new_len = ( len ? ( len + DHCP_OPTION_HEADER_LEN ) : 0 );
 
+       /* Return NULL if no options block specified */
+       if ( ! options )
+               return NULL;
+
        /* Find old instance of this option, if any */
        option = find_dhcp_option_with_encap ( options, tag, &encapsulator );
        if ( option ) {
                old_len = dhcp_option_len ( option );
-               DBG ( "Resizing DHCP option %s from length %d to %d in block "
+               DBG ( "Resizing DHCP option %s from length %d to %zd in block "
                      "%p\n", dhcp_tag_name (tag), option->len, len, options );
        } else {
                old_len = 0;
-               DBG ( "Creating DHCP option %s (length %d) in block %p\n",
+               DBG ( "Creating DHCP option %s (length %zd) in block %p\n",
                      dhcp_tag_name ( tag ), len, options );
        }
        
        /* Ensure that encapsulator exists, if required */
+       insertion_point = options->data;
        if ( DHCP_IS_ENCAP_OPT ( tag ) ) {
                if ( ! encapsulator )
                        encapsulator = set_dhcp_option ( options, encap_tag,
@@ -460,6 +512,45 @@ unsigned long find_global_dhcp_num_option ( unsigned int tag ) {
        return dhcp_num_option ( find_global_dhcp_option ( tag ) );
 }
 
+/**
+ * Find DHCP IPv4-address option, and return its value
+ *
+ * @v options          DHCP options block
+ * @v tag              DHCP option tag to search for
+ * @v inp              IPv4 address to fill in
+ * @ret value          Numerical value of the option, or 0 if not found
+ *
+ * This function exists merely as a notational shorthand for a call to
+ * find_dhcp_option() followed by a call to dhcp_ipv4_option().  It is
+ * not possible to distinguish between the cases "option not found"
+ * and "option has a value of 0.0.0.0" using this function; if this
+ * matters to you then issue the two constituent calls directly and
+ * check that find_dhcp_option() returns a non-NULL value.
+ */
+void find_dhcp_ipv4_option ( struct dhcp_option_block *options,
+                            unsigned int tag, struct in_addr *inp ) {
+       dhcp_ipv4_option ( find_dhcp_option ( options, tag ), inp );
+}
+
+/**
+ * Find DHCP IPv4-address option, and return its value
+ *
+ * @v options          DHCP options block
+ * @v tag              DHCP option tag to search for
+ * @v inp              IPv4 address to fill in
+ * @ret value          Numerical value of the option, or 0 if not found
+ *
+ * This function exists merely as a notational shorthand for a call to
+ * find_dhcp_option() followed by a call to dhcp_ipv4_option().  It is
+ * not possible to distinguish between the cases "option not found"
+ * and "option has a value of 0.0.0.0" using this function; if this
+ * matters to you then issue the two constituent calls directly and
+ * check that find_dhcp_option() returns a non-NULL value.
+ */
+void find_global_dhcp_ipv4_option ( unsigned int tag, struct in_addr *inp ) {
+       dhcp_ipv4_option ( find_global_dhcp_option ( tag ), inp );
+}
+
 /**
  * Delete DHCP option
  *
@@ -473,3 +564,36 @@ void delete_dhcp_option ( struct dhcp_option_block *options,
                          unsigned int tag ) {
        set_dhcp_option ( options, tag, NULL, 0 );
 }
+
+/**
+ * Apply DHCP options
+ *
+ * @v options          DHCP options block, or NULL
+ * @ret rc             Return status code
+ */
+int apply_dhcp_options ( struct dhcp_option_block *options ) {
+       struct in_addr tftp_server;
+       struct uri *uri;
+       char uri_string[32];
+
+       /* Set current working URI based on TFTP server */
+       find_dhcp_ipv4_option ( options, DHCP_EB_SIADDR, &tftp_server );
+       snprintf ( uri_string, sizeof ( uri_string ),
+                  "tftp://%s/", inet_ntoa ( tftp_server ) );
+       uri = parse_uri ( uri_string );
+       if ( ! uri )
+               return -ENOMEM;
+       churi ( uri );
+       uri_put ( uri );
+
+       return 0;
+}
+
+/**
+ * Apply global DHCP options
+ *
+ * @ret rc             Return status code
+ */
+int apply_global_dhcp_options ( void ) {
+       return apply_dhcp_options ( NULL );
+}