5.3. Describing Dynamic Software Updates

For every dynamic software update patch that will be produced, a file must be provided that describes how the update should be applied. This file describes:

These four items are described next.

5.3.1. Describing Function Updates

The functions that should be updated need to be described to the dynamic software update runtime. Figure 5-1 shows an example describing the updated functions when updating vsFTPd from version 2.0.4 to version 2.0.5.

Figure 5-1. Describing function updates for vsFTPd from 2.0.4 to 2.0.5.

#include "hcu_mappings.h"

hcu_mapping_update_description_t mapping_updates_v2[] = { { "main", "main" } };
hcu_mapping_function_update_description_t mapping_function_updates_v2[] = {
  { "str_locate_text_reverse", "str_locate_text_reverse", 0 },
  { "emit_greeting", "emit_greeting", 0 },
  { "handle_login", "handle_login", 0 },
  { "str_locate_chars", "str_locate_chars", 0 },
  { "vsf_privop_do_login", "vsf_privop_do_login", 0 },
  { "vsf_remove_uwtmp", "vsf_remove_uwtmp", 0 },
  { "handle_retr", "handle_retr", 0 },
  { "vsf_sysutil_connect_timeout", "vsf_sysutil_connect_timeout", 0 },
  { "handle_upload_common", "handle_upload_common", 0 },
  { "handle_user_command", "handle_user_command", 0 },
  { "main", "main", 1 },
  { "handle_mdtm", "handle_mdtm", 0 },
  { "handle_size", "handle_size", 0 },
  { "vsf_insert_uwtmp", "vsf_insert_uwtmp", 0 },
  { "handle_feat", "handle_feat", 0 },
  { "str_locate_text", "str_locate_text", 0 },
  { "get_unique_filename", "get_unique_filename", 0 },
  { "vsf_sysutil_chroot", "vsf_sysutil_chroot", 0 },
  { "vsf_sysdep_check_auth", "vsf_sysdep_check_auth", 0 },
  { "vsf_sysutil_tzset", "vsf_sysutil_tzset", 0 },
  { "handle_pass_command", "handle_pas_command", 0 },
  { "vsf_ls_populate_dir_list", "vsf_ls_populate_dir_list", 0 },
  { "calc_num_send", "calc_num_send", 0 },
  { "handle_stat", "handle_stat", 0 },
  { "vsf_privop_do_file_chown", "vsf_privop_do_file_chown", 0 }
};
           
The description file defines two variables of two key datatypes:

Warning

But wait! How did a user produce the list of function updates?

The patch generator, described in Section 5.4, can produce the list of modified functions when invoked with empty variable definitions of the two required datatypes hcu_mapping_update_description_t and hcu_mapping_function_update_description_t. It is expected that a user will first run the patch generator to produce the list of function updates, and then manually produce the update description file.

But why? Isn't the patch generator capable of producing the entire update description file?

The patch generator can produce the entire update description file, but that would be presumptuous. Producing the entire list of function updates in the file would guarantee that a program is representation consistent: the running version matches the source code. However, the user may not desire an update to be representation consistent for various reasons. A user may want to apply an update to only a small collection of functions. For example, the user may want to apply a security fix or to avoid introducing, as part of the update, additional known defects that are present in the updated version of the program.

Conclusion: Allowing users to manually produce a customized update description file separates policy from mechanism in the patch generator.

There are plans to enhance the patch generator to produce a template update description file that the user may customize to produce the final file.

5.3.2. Describing Datatype Updates

Datatype updates can affect both global variables and local variables in functions that use the datatype. Datatype updates are automatically produced by the patch generator as described in Section 5.4. To complement incomplete datatype updates a user can manually write datatype transformers in the mappings file. Datatype transformer names must contain the special prefix HCU_datatype_transformation__ and to be invoked from the special function HCU_datatype_transformations_function_.

For example, the parseconf_uint_array variable is an array than has had its size extended in the newer version 2.0.5. The values of this array are preserved in the updated parseconf_uint_array_v205 variable for version 2.0.5, as shown in Figure 5-2.

Figure 5-2. Transforming datatypes for vsFTPd from 2.0.4 to 2.0.5.

void HCU_datatype_transformation__struct__parseconf_uint_setting__arraysize17_to_struct__parseconf_uint_setting__arraysize20(struct parseconf_uint_setting (*old)[17] ,
                                                                                                                             struct parseconf_uint_setting (*new)[20] ) 
{ long array_counter ;

  {
  array_counter = 0;
  while (1) {
    HCU_datatype_transformation__struct__parseconf_uint_setting__arraysize17_to_struct__parseconf_uint_setting__arraysize20__element(& (*old)[array_counter],
                                                                                                                                     & (*new)[array_counter]);
    if (array_counter >= 17 - 1) {
      break;
    }
    array_counter ++;
  }

  // Must extend the array
  // NOTE: The following 11 statements are not automatically generated yet
  ((*new)[16]).p_setting_name = malloc(strlen("delay_failed_login") + 1);
  memcpy(&((*new)[16]).p_setting_name, "delay_failed_login" "\0", strlen("delay_failed_login") + 1);
  ((*new)[16]).p_variable = &tunable_delay_failed_login;

  ((*new)[17]).p_setting_name = malloc(strlen("delay_successful_login") + 1);
  memcpy(&((*new)[17]).p_setting_name, "delay_successful_login" "\0", strlen("delay_successful_login") + 1);
  ((*new)[17]).p_variable = &tunable_delay_successful_login;

  ((*new)[18]).p_setting_name = malloc(strlen("max_login_fails") + 1);
  memcpy(&((*new)[18]).p_setting_name, "max_login_fails" "\0", strlen("max_login_fails") + 1);
  ((*new)[18]).p_variable = &tunable_max_login_fails;

  ((*new)[19]).p_setting_name = 0;
  ((*new)[19]).p_variable = 0;

}
}

int HCU_datatype_transformations_function()  __attribute__((__HCU_ATTRIBUTE_NON_UPDATEABLE__)) ;
{ 

  HCU_datatype_transformation__struct__parseconf_uint_setting__arraysize17_to_struct__parseconf_uint_setting__arraysize20(& parseconf_uint_array,
                                                                                                                          & parseconf_uint_array_v205);

  // NOTE: The following 3 statements are not automatically generated yet
  tunable_delay_failed_login = 1;
  tunable_delay_successful_login = 0;
  tunable_max_login_fails = 3;

  printf("HCU_datatype_transformations_function_v205 executed\n");
  return (0);
}
       

5.3.3. Describing Execution Continuation

UpStare is able to update a running algorithm midstream its execution and resume from a different point (not necessarily the beginning) of another algorithm that is behaviorally equivalent: an algorithm that aims to produce the same results while reusing existing progress. This capability requires user assistance. A user needs to define the mapping of continuation points in the original program to continuation points in the new version.

Continuation points are uniquely identified with an integer enumeration starting from 0 for every program function. This enumeration is embedded in a .cil.c file which contains the updateable source code. The continuation points of each function are enumerated in the beginning of the function (though the function name now includes the postfix _vXXX, where XXX is the version number) as case statements in a big switch statement.

Tip

There are plans to improve the identification (not the selection) of continuation points to use strings rather than numeric ids. This will further minimize the input needed by a user in defining continuation mappings.

For example, for vsFTPd 2.0.4 the update source code is prepared in vsftpd-2.0.4/vsftpd.cil.c. To view the continuation points of function vsf_standalone_main, a user should look at the big switch statement in the beginning of function vsf_standalone_main_v204, which is shown in part in Figure 5-3.

Figure 5-3. Viewing the execution continuation points of vsf_standalone_main in vsFTPd 2.0.4.

struct vsf_client_launch vsf_standalone_main_v204(void)
{

 ...

  switch (__cil_tmp20) {
  case 0:
  goto vsf_standalone_main_entrypoint;
  case 1:
  goto hcu_try_to_update_1_after;
  case 2:
  goto vsf_sysutil_get_ipaddr_size_2_after;
  case 3:
  goto die_3_after;
  case 4:
  goto vsf_sysutil_fork_4_after;

...

           

Figure 5-4 shows an example describing execution continuation when updating Bubblesort and continuing execution with Heapsort.

Figure 5-4. Describing execution continuation when updating from Bubblesort to Heapsort.

#include "hcu_mappings.h"
#include "hcu_headers.h"

hcu_mapping_update_description_t mapping_updates_v2[] = { { "main", "main" } };
hcu_mapping_function_update_description_t mapping_function_updates_v2[] = {
  { "main", "main", 1 }
};
hcu_mapping_algorithmic_equivalence_t mapping_equivalence_v2_bubblesort[] = {
  { "bubbleSort",
    "heapSort",
    1,
    { { 2, 1 } }
  }
};

struct hcu_stack_local_bubbleSort_v1_s {
   int i ;
   int j ;
   int temp ;
   struct hcu_stack_frame_fields_s hcu_stack_frame_fields ;
};

struct hcu_stack_local_heapSort_v2_s {
   int i ;
   int temp ;
   struct hcu_stack_frame_fields_s hcu_stack_frame_fields ;
};

struct hcu_function_formal_heapSort_v2_s {
   int *numbers ;
   int array_size ;
};

void HCU_stack_transformer__heapSort(void *transform_stack_to ,
                                     void *transform_stack_from ,
                                     void *transform_params_to ) 
{
  struct hcu_stack_local_heapSort_v2_s *stack_to ;
  struct hcu_stack_local_bubbleSort_v1_s *stack_from ;
  struct hcu_function_formal_heapSort_v2_s *params_to ;

  stack_to = (struct hcu_stack_local_heapSort_v2_s *)transform_stack_to;
  stack_from = (struct hcu_stack_local_bubbleSort_v1_s *)transform_stack_from;
  params_to = (struct hcu_function_formal_heapSort_v2_s *)transform_params_to;

  /* Continue the heapsort algorithm from the current iteration (redo
     the last iteration). Don't restart it from scratch.

     Our bubbleSort implementation processes an array from the
     end. Thus we simply have to shrink the array size to define the
     new bounds of the array for heapSort. */
  params_to->array_size = stack_from->i + 1;
  hcu_copy_stack_frame_fields(&stack_to->hcu_stack_frame_fields,
                              &stack_from->hcu_stack_frame_fields);
}
           
The description file defines a variable of an important datatype:

The description file also defines a stack-state transformer that will allow Heapsort to continue execution from where Bubblesort stopped, reusing the current program state. The transformer name must contain the special prefix HCU_stack_transformer__ and accepts three special parameters:

The struct definitions for the stack of the old and new functions, and the formal parameters of the new function also need to be defined. These definitions can also be produced by the patch generator, as discussed in Section 5.3.1. The stack-state transformer invokes a special function that preserves bookkeeping information maintained by the runtime:

Tip

So what happens in this example?

The stack of the updated function heapSort does not have state preserved from the stack of the old function bubbleSort at all. The stack of the old function is only consulted to change the formal parameters of the new function, and the stack of the new function remains uninitialized. Essentially, the new function continues execution by taking as input a smaller array of numbers to be sorted: it continues sorting from where Bubblesort stopped.

5.3.4. Describing Update Constraints

Defining update constraints can help reduce the amount of state that needs to be mapped from the old version of an application to the new version. Depending on the updating model used, defining update constraints can also help enforce runtime safety.

If the updating model requested is to apply updates lazily, it is often necessary to define constraints that enforce type-safety and transaction-safety. The lazy updating model is enabled either using the HCU_STATIC_REQUEST_UPDATE_LAZY() call or by invoking the tool hcuapply with the command-line parameter--update-model=lazy.

Tip

Defining update constraints for type-safety is not necessary if the updating model requested is to apply updates immediately.

However, defining update constraints for transaction-safety can be useful both for applying updates immediately and lazily.

Figure 5-5 shows an example describing update constraints.

Figure 5-5. Describing update constraints.

#include "hcu_mappings.h"
#include "hcu_safety_constraints.h"

hcu_safety_constraint_t hcu_safety_constraints_v2[] = {
  { "main",
    "functionA",
    1,
    { { -1, -1, 0 } }
  }
};
           
The description file defines a variable of an important datatype:

Tip

So what happens in this example?

The constraint prohibits updates anywhere inside the function functionA for the main thread.