diff --git a/src/core/nvo.c b/src/core/nvo.c index 1a886c0f8..9b325db75 100644 --- a/src/core/nvo.c +++ b/src/core/nvo.c @@ -133,7 +133,8 @@ static void nvo_init_dhcpopts ( struct nvo_block *nvo ) { memset ( nvo->data, 0, nvo->total_len ); } - dhcpopt_init ( &nvo->dhcpopts, options_data, options_len ); + dhcpopt_init ( &nvo->dhcpopts, options_data, options_len, + dhcpopt_no_realloc ); } /** diff --git a/src/include/ipxe/dhcpopts.h b/src/include/ipxe/dhcpopts.h index d88036e3a..fe07d903d 100644 --- a/src/include/ipxe/dhcpopts.h +++ b/src/include/ipxe/dhcpopts.h @@ -19,16 +19,23 @@ struct dhcp_options { size_t used_len; /** Option block allocated length */ size_t alloc_len; + /** Reallocate option block raw data + * + * @v options DHCP option block + * @v len New length + * @ret rc Return status code + */ + int ( * realloc ) ( struct dhcp_options *options, size_t len ); }; extern int dhcpopt_store ( struct dhcp_options *options, unsigned int tag, const void *data, size_t len ); -extern int dhcpopt_extensible_store ( struct dhcp_options *options, - unsigned int tag, - const void *data, size_t len ); extern int dhcpopt_fetch ( struct dhcp_options *options, unsigned int tag, void *data, size_t len ); extern void dhcpopt_init ( struct dhcp_options *options, - void *data, size_t alloc_len ); + void *data, size_t alloc_len, + int ( * realloc ) ( struct dhcp_options *options, + size_t len ) ); +extern int dhcpopt_no_realloc ( struct dhcp_options *options, size_t len ); #endif /* _IPXE_DHCPOPTS_H */ diff --git a/src/net/dhcpopts.c b/src/net/dhcpopts.c index 214a8244c..d1330eae5 100644 --- a/src/net/dhcpopts.c +++ b/src/net/dhcpopts.c @@ -169,6 +169,17 @@ static int find_dhcp_option_with_encap ( struct dhcp_options *options, return -ENOENT; } +/** + * Refuse to reallocate DHCP option block + * + * @v options DHCP option block + * @v len New length + * @ret rc Return status code + */ +int dhcpopt_no_realloc ( struct dhcp_options *options, size_t len ) { + return ( ( len <= options->alloc_len ) ? 0 : -ENOSPC ); +} + /** * Resize a DHCP option * @@ -177,46 +188,44 @@ static int find_dhcp_option_with_encap ( struct dhcp_options *options, * @v encap_offset Offset of encapsulating offset (or -ve for none) * @v old_len Old length (including header) * @v new_len New length (including header) - * @v can_realloc Can reallocate options data if necessary * @ret rc Return status code */ static int resize_dhcp_option ( struct dhcp_options *options, int offset, int encap_offset, - size_t old_len, size_t new_len, - int can_realloc ) { + size_t old_len, size_t new_len ) { struct dhcp_option *encapsulator; struct dhcp_option *option; ssize_t delta = ( new_len - old_len ); - size_t new_options_len; + size_t old_alloc_len; + size_t new_used_len; size_t new_encapsulator_len; - void *new_data; void *source; void *dest; void *end; + int rc; - /* Check for sufficient space, and update length fields */ + /* Check for sufficient space */ if ( new_len > DHCP_MAX_LEN ) { DBGC ( options, "DHCPOPT %p overlength option\n", options ); return -ENOSPC; } - new_options_len = ( options->used_len + delta ); - if ( new_options_len > options->alloc_len ) { - /* Reallocate options block if allowed to do so. */ - if ( can_realloc ) { - new_data = realloc ( options->data, new_options_len ); - if ( ! new_data ) { - DBGC ( options, "DHCPOPT %p could not " - "reallocate to %zd bytes\n", options, - new_options_len ); - return -ENOMEM; - } - options->data = new_data; - options->alloc_len = new_options_len; - } else { - DBGC ( options, "DHCPOPT %p out of space\n", options ); - return -ENOMEM; + new_used_len = ( options->used_len + delta ); + + /* Expand options block, if necessary */ + if ( new_used_len > options->alloc_len ) { + /* Reallocate options block */ + old_alloc_len = options->alloc_len; + if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ){ + DBGC ( options, "DHCPOPT %p could not reallocate to " + "%zd bytes\n", options, new_used_len ); + return rc; } + /* Clear newly allocated space */ + memset ( ( options->data + old_alloc_len ), 0, + ( options->alloc_len - old_alloc_len ) ); } + + /* Update encapsulator, if applicable */ if ( encap_offset >= 0 ) { encapsulator = dhcp_option ( options, encap_offset ); new_encapsulator_len = ( encapsulator->len + delta ); @@ -227,7 +236,9 @@ static int resize_dhcp_option ( struct dhcp_options *options, } encapsulator->len = new_encapsulator_len; } - options->used_len = new_options_len; + + /* Update used length */ + options->used_len = new_used_len; /* Move remainder of option data */ option = dhcp_option ( options, offset ); @@ -236,6 +247,15 @@ static int resize_dhcp_option ( struct dhcp_options *options, end = ( options->data + options->alloc_len ); memmove ( dest, source, ( end - dest ) ); + /* Shrink options block, if applicable */ + if ( new_used_len < options->alloc_len ) { + if ( ( rc = options->realloc ( options, new_used_len ) ) != 0 ){ + DBGC ( options, "DHCPOPT %p could not reallocate to " + "%zd bytes\n", options, new_used_len ); + return rc; + } + } + return 0; } @@ -246,7 +266,6 @@ static int resize_dhcp_option ( struct dhcp_options *options, * @v tag DHCP option tag * @v data New value for DHCP option * @v len Length of value, in bytes - * @v can_realloc Can reallocate options data if necessary * @ret offset Offset of DHCP option, or negative error * * Sets the value of a DHCP option within the options block. The @@ -258,9 +277,8 @@ static int resize_dhcp_option ( struct dhcp_options *options, * be left with its original value. */ static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag, - const void *data, size_t len, - int can_realloc ) { - static const uint8_t empty_encapsulator[] = { DHCP_END }; + const void *data, size_t len ) { + static const uint8_t empty_encap[] = { DHCP_END }; int offset; int encap_offset = -1; int creation_offset; @@ -291,10 +309,12 @@ static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag, /* Ensure that encapsulator exists, if required */ if ( encap_tag ) { - if ( encap_offset < 0 ) - encap_offset = set_dhcp_option ( options, encap_tag, - empty_encapsulator, 1, - can_realloc ); + if ( encap_offset < 0 ) { + encap_offset = + set_dhcp_option ( options, encap_tag, + empty_encap, + sizeof ( empty_encap ) ); + } if ( encap_offset < 0 ) return encap_offset; creation_offset = ( encap_offset + DHCP_OPTION_HEADER_LEN ); @@ -306,8 +326,7 @@ static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag, /* Resize option to fit new data */ if ( ( rc = resize_dhcp_option ( options, offset, encap_offset, - old_len, new_len, - can_realloc ) ) != 0 ) + old_len, new_len ) ) != 0 ) return rc; /* Copy new data into option, if applicable */ @@ -322,7 +341,7 @@ static int set_dhcp_option ( struct dhcp_options *options, unsigned int tag, if ( encap_offset >= 0 ) { option = dhcp_option ( options, encap_offset ); if ( option->len <= 1 ) - set_dhcp_option ( options, encap_tag, NULL, 0, 0 ); + set_dhcp_option ( options, encap_tag, NULL, 0 ); } return offset; @@ -341,26 +360,7 @@ int dhcpopt_store ( struct dhcp_options *options, unsigned int tag, const void *data, size_t len ) { int offset; - offset = set_dhcp_option ( options, tag, data, len, 0 ); - if ( offset < 0 ) - return offset; - return 0; -} - -/** - * Store value of DHCP option setting, extending options block if necessary - * - * @v options DHCP option block - * @v tag Setting tag number - * @v data Setting data, or NULL to clear setting - * @v len Length of setting data - * @ret rc Return status code - */ -int dhcpopt_extensible_store ( struct dhcp_options *options, unsigned int tag, - const void *data, size_t len ) { - int offset; - - offset = set_dhcp_option ( options, tag, data, len, 1 ); + offset = set_dhcp_option ( options, tag, data, len ); if ( offset < 0 ) return offset; return 0; @@ -428,16 +428,19 @@ static void dhcpopt_update_used_len ( struct dhcp_options *options ) { * @v options Uninitialised DHCP option block * @v data Memory for DHCP option data * @v alloc_len Length of memory for DHCP option data + * @v realloc DHCP option block reallocator * * The memory content must already be filled with valid DHCP options. * A zeroed block counts as a block of valid DHCP options. */ -void dhcpopt_init ( struct dhcp_options *options, void *data, - size_t alloc_len ) { +void dhcpopt_init ( struct dhcp_options *options, void *data, size_t alloc_len, + int ( * realloc ) ( struct dhcp_options *options, + size_t len ) ) { /* Fill in fields */ options->data = data; options->alloc_len = alloc_len; + options->realloc = realloc; /* Update length */ dhcpopt_update_used_len ( options ); diff --git a/src/net/dhcppkt.c b/src/net/dhcppkt.c index e043bb5d4..237c3e2cf 100644 --- a/src/net/dhcppkt.c +++ b/src/net/dhcppkt.c @@ -267,7 +267,8 @@ void dhcppkt_init ( struct dhcp_packet *dhcppkt, struct dhcphdr *data, ref_init ( &dhcppkt->refcnt, NULL ); dhcppkt->dhcphdr = data; dhcpopt_init ( &dhcppkt->options, &dhcppkt->dhcphdr->options, - ( len - offsetof ( struct dhcphdr, options ) ) ); + ( len - offsetof ( struct dhcphdr, options ) ), + dhcpopt_no_realloc ); settings_init ( &dhcppkt->settings, &dhcppkt_settings_operations, &dhcppkt->refcnt, 0 ); }