Skip to content

Commit

Permalink
[dhcp] Allow use of custom reallocation functions for DHCP option blocks
Browse files Browse the repository at this point in the history
Allow functions other than realloc() to be used to reallocate DHCP
option block data, and specify the reallocation function at the time
of calling dhcpopt_init().

Signed-off-by: Michael Brown <mcb30@ipxe.org>
  • Loading branch information
mcb30 committed Jan 11, 2011
1 parent 310d46c commit 17b6a3c
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 62 deletions.
3 changes: 2 additions & 1 deletion src/core/nvo.c
Expand Up @@ -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 );
}

/**
Expand Down
15 changes: 11 additions & 4 deletions src/include/ipxe/dhcpopts.h
Expand Up @@ -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 */
115 changes: 59 additions & 56 deletions src/net/dhcpopts.c
Expand Up @@ -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
*
Expand All @@ -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 );
Expand All @@ -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 );
Expand All @@ -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;
}

Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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 );
Expand All @@ -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 */
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
3 changes: 2 additions & 1 deletion src/net/dhcppkt.c
Expand Up @@ -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 );
}

0 comments on commit 17b6a3c

Please sign in to comment.