minix/minix/servers/rs/update.c
David van Moolenbroek d91f738bd8 Kernel: export clock information on kernel page
Please note that this information is for use by system services only!
The clock facility is not ready to be used directly by userland, and
thus, this kernel page extension is NOT part of the userland ABI.

For service programmers' convenience, change the prototype of the
getticks(3) to return the uptime clock value directly, since the call
can no longer fail.

Correct the sys_times(2) reply message to use the right field type
for the boot time.

Restructure the kernel internals a bit so as to have all the clock
stuff closer together.

Change-Id: Ifc050b7bd253aecbe46e3bd7d7cc75bd86e45555
2015-09-23 12:00:46 +00:00

1012 lines
34 KiB
C

#include "inc.h"
/*===========================================================================*
* rupdate_clear_upds *
*===========================================================================*/
void rupdate_clear_upds()
{
/* Clear the update chain and the global update descriptor. */
struct rprocupd *prev_rpupd, *rpupd;
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, rpupd,
if(prev_rpupd) {
rupdate_upd_clear(prev_rpupd);
}
);
rupdate_upd_clear(rupdate.last_rpupd);
RUPDATE_CLEAR();
}
/*===========================================================================*
* rupdate_add_upd *
*===========================================================================*/
void rupdate_add_upd(struct rprocupd* rpupd)
{
/* Add an update descriptor to the update chain. */
struct rprocupd *prev_rpupd, *walk_rpupd;
endpoint_t ep;
int lu_flags;
/* In order to allow multicomponent-with-VM live updates to be processed
* correctly, we perform partial sorting on the chain: RS is to be last (if
* present), VM is to be right before it (if present), and all the other
* processes are to be at the start of the chain.
*/
ep = rpupd->rp->r_pub->endpoint;
assert(rpupd->next_rpupd == NULL);
assert(rpupd->prev_rpupd == NULL);
/* Determine what element to insert after, if not at the head. */
prev_rpupd = rupdate.last_rpupd;
if (prev_rpupd != NULL && ep != RS_PROC_NR &&
prev_rpupd->rp->r_pub->endpoint == RS_PROC_NR)
prev_rpupd = prev_rpupd->prev_rpupd;
if (prev_rpupd != NULL && ep != RS_PROC_NR && ep != VM_PROC_NR &&
prev_rpupd->rp->r_pub->endpoint == VM_PROC_NR)
prev_rpupd = prev_rpupd->prev_rpupd;
/* Perform the insertion. */
if (prev_rpupd == NULL) {
rpupd->next_rpupd = rupdate.first_rpupd;
rupdate.first_rpupd = rupdate.curr_rpupd = rpupd;
} else {
rpupd->next_rpupd = prev_rpupd->next_rpupd;
rpupd->prev_rpupd = prev_rpupd;
prev_rpupd->next_rpupd = rpupd;
}
if (rpupd->next_rpupd != NULL)
rpupd->next_rpupd->prev_rpupd = rpupd;
else
rupdate.last_rpupd = rpupd;
rupdate.num_rpupds++;
/* Propagate relevant flags from the new descriptor. */
lu_flags = rpupd->lu_flags & (SEF_LU_INCLUDES_VM|SEF_LU_INCLUDES_RS|SEF_LU_MULTI);
if(lu_flags) {
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, walk_rpupd,
walk_rpupd->lu_flags |= lu_flags;
walk_rpupd->init_flags |= lu_flags;
);
}
/* Set VM/RS update descriptor pointers. */
if(!rupdate.vm_rpupd && (lu_flags & SEF_LU_INCLUDES_VM)) {
rupdate.vm_rpupd = rpupd;
}
else if(!rupdate.rs_rpupd && (lu_flags & SEF_LU_INCLUDES_RS)) {
rupdate.rs_rpupd = rpupd;
}
}
/*===========================================================================*
* rupdate_set_new_upd_flags *
*===========================================================================*/
void rupdate_set_new_upd_flags(struct rprocupd* rpupd)
{
/* Set multi-component update flags. */
if(rupdate.num_rpupds > 0) {
rpupd->lu_flags |= SEF_LU_MULTI;
rpupd->init_flags |= SEF_LU_MULTI;
}
/* Propagate relevant flags from last service under update (if any). */
if(rupdate.last_rpupd) {
int lu_flags = rupdate.last_rpupd->lu_flags & (SEF_LU_INCLUDES_VM|SEF_LU_INCLUDES_RS);
rpupd->lu_flags |= lu_flags;
rpupd->init_flags |= lu_flags;
}
if(UPD_IS_PREPARING_ONLY(rpupd)) {
return;
}
/* Set VM/RS update flags. */
if(rpupd->rp->r_pub->endpoint == VM_PROC_NR) {
rpupd->lu_flags |= SEF_LU_INCLUDES_VM;
rpupd->init_flags |= SEF_LU_INCLUDES_VM;
}
else if(rpupd->rp->r_pub->endpoint == RS_PROC_NR) {
rpupd->lu_flags |= SEF_LU_INCLUDES_RS;
rpupd->init_flags |= SEF_LU_INCLUDES_RS;
}
}
/*===========================================================================*
* rupdate_upd_init *
*===========================================================================*/
void rupdate_upd_init(struct rprocupd* rpupd, struct rproc *rp)
{
/* Initialize an update descriptor for a given service. */
memset(rpupd, 0, sizeof(*(rpupd)));
rpupd->prepare_state_data_gid = GRANT_INVALID;
rpupd->prepare_state_data.ipcf_els_gid = GRANT_INVALID;
rpupd->prepare_state_data.eval_gid = GRANT_INVALID;
rpupd->state_endpoint = NONE;
rpupd->rp = rp;
}
/*===========================================================================*
* rupdate_upd_clear *
*===========================================================================*/
void rupdate_upd_clear(struct rprocupd* rpupd)
{
/* Clear an update descriptor. */
if(rpupd->rp->r_new_rp) {
cleanup_service(rpupd->rp->r_new_rp);
}
if(rpupd->prepare_state_data_gid != GRANT_INVALID) {
cpf_revoke(rpupd->prepare_state_data_gid);
}
if(rpupd->prepare_state_data.size > 0) {
if(rpupd->prepare_state_data.ipcf_els_gid != GRANT_INVALID) {
cpf_revoke(rpupd->prepare_state_data.ipcf_els_gid);
}
if(rpupd->prepare_state_data.eval_gid != GRANT_INVALID) {
cpf_revoke(rpupd->prepare_state_data.eval_gid);
}
if(rpupd->prepare_state_data.ipcf_els) {
free(rpupd->prepare_state_data.ipcf_els);
}
if(rpupd->prepare_state_data.eval_addr) {
free(rpupd->prepare_state_data.eval_addr);
}
}
rupdate_upd_init(rpupd,NULL);
}
/*===========================================================================*
* rupdate_upd_move *
*===========================================================================*/
void rupdate_upd_move(struct rproc* src_rp, struct rproc* dst_rp)
{
/* Move an update descriptor from one service instance to another. */
dst_rp->r_upd = src_rp->r_upd;
dst_rp->r_upd.rp = dst_rp;
if(src_rp->r_new_rp) {
assert(!dst_rp->r_new_rp);
dst_rp->r_new_rp = src_rp->r_new_rp;
dst_rp->r_new_rp->r_old_rp = dst_rp;
}
if(dst_rp->r_upd.prev_rpupd) dst_rp->r_upd.prev_rpupd->next_rpupd = &dst_rp->r_upd;
if(dst_rp->r_upd.next_rpupd) dst_rp->r_upd.next_rpupd->prev_rpupd = &dst_rp->r_upd;
if(rupdate.first_rpupd == &src_rp->r_upd) rupdate.first_rpupd = &dst_rp->r_upd;
if(rupdate.last_rpupd == &src_rp->r_upd) rupdate.last_rpupd = &dst_rp->r_upd;
rupdate_upd_init(&src_rp->r_upd, NULL);
src_rp->r_new_rp = NULL;
}
/*===========================================================================*
* request_prepare_update_service_debug *
*===========================================================================*/
void request_prepare_update_service_debug(char *file, int line,
struct rproc *rp, int state)
{
/* Request a service to prepare/cancel the update. */
message m;
struct rprocpub *rpub;
int no_reply;
rpub = rp->r_pub;
if(state != SEF_LU_STATE_NULL) {
struct rprocupd *rpupd = &rp->r_upd;
rpupd->prepare_tm = getticks();
if(!UPD_IS_PREPARING_ONLY(rpupd)) {
assert(rp->r_new_rp);
rp->r_flags |= RS_UPDATING;
rp->r_new_rp->r_flags |= RS_UPDATING;
}
else {
assert(!rp->r_new_rp);
}
m.m_rs_update.flags = rpupd->lu_flags;
m.m_rs_update.state_data_gid = rpupd->prepare_state_data_gid;
if(rs_verbose)
printf("RS: %s being requested to prepare for the %s at %s:%d\n",
srv_to_string(rp), srv_upd_to_string(rpupd), file, line);
}
else {
if(rs_verbose)
printf("RS: %s being requested to cancel the update at %s:%d\n",
srv_to_string(rp), file, line);
}
/* Request to prepare for the update or cancel the update. */
m.m_type = RS_LU_PREPARE;
m.m_rs_update.state = state;
no_reply = !(rp->r_flags & RS_PREPARE_DONE);
rs_asynsend(rp, &m, no_reply);
}
/*===========================================================================*
* srv_update *
*===========================================================================*/
int srv_update(endpoint_t src_e, endpoint_t dst_e, int sys_upd_flags)
{
int r = OK;
/* Ask VM to swap the slots of the two processes and tell the kernel to
* do the same. If VM is being updated, only perform the kernel
* part of the call. The new instance of VM will do the rest at
* initialization time. If a multi-component update includes VM, let VM
* handle updates at state transfer time and rollbacks afterwards.
*/
if(src_e == VM_PROC_NR) {
if(rs_verbose)
printf("RS: executing sys_update(%d, %d)\n", src_e, dst_e);
r = sys_update(src_e, dst_e,
sys_upd_flags & SF_VM_ROLLBACK ? SYS_UPD_ROLLBACK : 0);
}
else if(!RUPDATE_IS_UPD_VM_MULTI() || RUPDATE_IS_VM_INIT_DONE()) {
if(rs_verbose)
printf("RS: executing vm_update(%d, %d)\n", src_e, dst_e);
r = vm_update(src_e, dst_e, sys_upd_flags);
}
else {
if(rs_verbose)
printf("RS: skipping srv_update(%d, %d)\n", src_e, dst_e);
}
return r;
}
/*===========================================================================*
* update_service *
*===========================================================================*/
int update_service(src_rpp, dst_rpp, swap_flag, sys_upd_flags)
struct rproc **src_rpp;
struct rproc **dst_rpp;
int swap_flag;
int sys_upd_flags;
{
/* Update an existing service. */
int r;
struct rproc *src_rp;
struct rproc *dst_rp;
struct rprocpub *src_rpub;
struct rprocpub *dst_rpub;
int pid;
endpoint_t endpoint;
src_rp = *src_rpp;
dst_rp = *dst_rpp;
src_rpub = src_rp->r_pub;
dst_rpub = dst_rp->r_pub;
if(rs_verbose)
printf("RS: %s updating into %s\n",
srv_to_string(src_rp), srv_to_string(dst_rp));
/* Swap the slots of the two processes when asked to. */
if(swap_flag == RS_SWAP) {
if((r = srv_update(src_rpub->endpoint, dst_rpub->endpoint, sys_upd_flags)) != OK) {
return r;
}
}
/* Swap slots here as well. */
pid = src_rp->r_pid;
endpoint = src_rpub->endpoint;
swap_slot(&src_rp, &dst_rp);
/* Reassign pids and endpoints. */
src_rp->r_pid = dst_rp->r_pid;
src_rp->r_pub->endpoint = dst_rp->r_pub->endpoint;
rproc_ptr[_ENDPOINT_P(src_rp->r_pub->endpoint)] = src_rp;
dst_rp->r_pid = pid;
dst_rp->r_pub->endpoint = endpoint;
rproc_ptr[_ENDPOINT_P(dst_rp->r_pub->endpoint)] = dst_rp;
/* Update in-RS priv structs */
if ((r = sys_getpriv(&src_rp->r_priv, src_rp->r_pub->endpoint)) != OK)
panic("RS: update: could not update RS copies of priv of src: %d\n", r);
if ((r = sys_getpriv(&dst_rp->r_priv, dst_rp->r_pub->endpoint)) != OK)
panic("RS: update: could not update RS copies of priv of dst: %d\n", r);
/* Adjust input pointers. */
*src_rpp = src_rp;
*dst_rpp = dst_rp;
/* Make the new version active. */
activate_service(dst_rp, src_rp);
if(rs_verbose)
printf("RS: %s updated into %s\n",
srv_to_string(src_rp), srv_to_string(dst_rp));
return OK;
}
/*===========================================================================*
* rollback_service *
*===========================================================================*/
void rollback_service(struct rproc **new_rpp, struct rproc **old_rpp)
{
/* Rollback an updated service. */
struct rproc *rp;
int r = OK;
/* RS is special, we may only need to swap the slots to rollback. */
if((*old_rpp)->r_pub->endpoint == RS_PROC_NR) {
endpoint_t me = NONE;
char name[20];
int priv_flags, init_flags;
r = sys_whoami(&me, name, sizeof(name), &priv_flags, &init_flags);
assert(r == OK);
if(me != RS_PROC_NR) {
r = vm_update((*new_rpp)->r_pub->endpoint, (*old_rpp)->r_pub->endpoint, SF_VM_ROLLBACK);
if(rs_verbose)
printf("RS: %s performed rollback\n", srv_to_string(*new_rpp));
}
/* Since we may now have missed heartbeat replies, resend requests. */
for (rp = BEG_RPROC_ADDR; rp < END_RPROC_ADDR; rp++)
if (rp->r_flags & RS_ACTIVE)
rp->r_check_tm = 0;
}
else {
int swap_flag = ((*new_rpp)->r_flags & RS_INIT_PENDING ? RS_DONTSWAP : RS_SWAP);
if(rs_verbose)
printf("RS: %s performs rollback\n", srv_to_string(*new_rpp));
if(swap_flag == RS_SWAP) {
/* Freeze the new instance to rollback safely. */
sys_privctl((*new_rpp)->r_pub->endpoint, SYS_PRIV_DISALLOW, NULL);
}
r = update_service(new_rpp, old_rpp, swap_flag, SF_VM_ROLLBACK);
}
assert(r == OK); /* can't fail */
}
/*===========================================================================*
* update_period *
*===========================================================================*/
void update_period(message *m_ptr)
{
/* Periodically check the status of the update (preparation phase). */
clock_t now = m_ptr->m_notify.timestamp;
short has_update_timed_out;
message m;
struct rprocupd *rpupd;
struct rproc *rp;
struct rprocpub *rpub;
rpupd = rupdate.curr_rpupd;
rp = rpupd->rp;
rpub = rp->r_pub;
/* See if a timeout has occurred. */
has_update_timed_out = (rpupd->prepare_maxtime > 0) && (now - rpupd->prepare_tm > rpupd->prepare_maxtime);
/* If an update timed out, end the update process and notify
* the old version that the update has been canceled. From now on, the old
* version will continue executing.
*/
if(has_update_timed_out) {
printf("RS: update failed: maximum prepare time reached\n");
end_update(EINTR, RS_CANCEL);
}
}
/*===========================================================================*
* start_update_prepare *
*===========================================================================*/
int start_update_prepare(int allow_retries)
{
/* Start the preparation phase of the update process. */
struct rprocupd *prev_rpupd, *rpupd;
struct rproc *rp, *new_rp;
int r;
if(!RUPDATE_IS_UPD_SCHEDULED()) {
return EINVAL;
}
if(!rs_is_idle()) {
printf("RS: not idle now, try again\n");
if(!allow_retries) {
abort_update_proc(EAGAIN);
}
return EAGAIN;
}
if(rs_verbose)
printf("RS: starting the preparation phase of the update process\n");
if(rupdate.rs_rpupd) {
assert(rupdate.rs_rpupd == rupdate.last_rpupd);
assert(rupdate.rs_rpupd->rp->r_pub->endpoint == RS_PROC_NR);
assert(!UPD_IS_PREPARING_ONLY(rupdate.rs_rpupd));
}
if(rupdate.vm_rpupd) {
assert(rupdate.vm_rpupd->rp->r_pub->endpoint == VM_PROC_NR);
assert(!UPD_IS_PREPARING_ONLY(rupdate.vm_rpupd));
}
/* If a multi-component update includes VM, fill information about old
* and new endpoints, as well as update flags. VM needs this to complete
* the update internally at state transfer time.
*/
if(RUPDATE_IS_UPD_VM_MULTI()) {
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, rpupd,
if(!UPD_IS_PREPARING_ONLY(rpupd)) {
rp = rpupd->rp;
new_rp = rp->r_new_rp;
assert(rp && new_rp);
rp->r_pub->old_endpoint = rpupd->state_endpoint;
rp->r_pub->new_endpoint = rp->r_pub->endpoint;
if(rpupd != rupdate.vm_rpupd && rpupd != rupdate.rs_rpupd) {
rp->r_pub->sys_flags |= SF_VM_UPDATE;
if(rpupd->lu_flags & SEF_LU_NOMMAP) {
rp->r_pub->sys_flags |= SF_VM_NOMMAP;
}
}
}
);
}
/* Request the first service to prepare for the update. */
if(start_update_prepare_next() == NULL) {
/* If we are done already, end the update now. */
end_update(OK, RS_REPLY);
return ESRCH;
}
return OK;
}
/*===========================================================================*
* start_update_prepare_next *
*===========================================================================*/
struct rprocupd* start_update_prepare_next()
{
/* Request the next service in the update chain to prepare for the update. */
struct rprocupd *rpupd, *prev_rpupd, *walk_rpupd;
struct rproc *rp, *new_rp;
if(!RUPDATE_IS_UPDATING()) {
rpupd = rupdate.first_rpupd;
}
else {
rpupd = rupdate.curr_rpupd->next_rpupd;
}
if(!rpupd) {
return NULL;
}
if (RUPDATE_IS_UPD_VM_MULTI() && rpupd == rupdate.vm_rpupd) {
/* We are doing a multicomponent live update that includes VM, and all
* services are now ready (and thereby stopped) except VM and possibly
* RS. This is the last point in time, and therefore also the best, that
* we can ask the (old) VM instance to do stuff for us, before we ask it
* to get ready as well: preallocate and pin memory, and copy over
* memory-mapped regions. Do this now, for all services except VM
* itself. In particular, also do it for RS, as we know that RS (yes,
* this service) is not going to create problems from here on.
*/
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, walk_rpupd,
if (UPD_IS_PREPARING_ONLY(walk_rpupd))
continue; /* skip prepare-only processes */
if (walk_rpupd == rupdate.vm_rpupd)
continue; /* skip VM */
rp = walk_rpupd->rp;
new_rp = rp->r_new_rp;
assert(rp && new_rp);
if (rs_verbose)
printf("RS: preparing VM for %s -> %s\n", srv_to_string(rp),
srv_to_string(new_rp));
/* Ask VM to prepare the new instance based on the old instance. */
vm_prepare(rp->r_pub->new_endpoint, new_rp->r_pub->endpoint,
rp->r_pub->sys_flags);
);
}
rupdate.flags |= RS_UPDATING;
while(1) {
rupdate.curr_rpupd = rpupd;
request_prepare_update_service(rupdate.curr_rpupd->rp, rupdate.curr_rpupd->prepare_state);
if(!UPD_IS_PREPARING_ONLY(rpupd)) {
/* Continue only if the current service requires a prepare-only update. */
break;
}
if(!rupdate.curr_rpupd->next_rpupd) {
/* Continue only if there are services left. */
break;
}
rpupd = rupdate.curr_rpupd->next_rpupd;
}
return rpupd;
}
/*===========================================================================*
* start_update *
*===========================================================================*/
int start_update()
{
/* Start the update phase of the update process. */
struct rprocupd *prev_rpupd, *rpupd;
int r, init_ready_pending=0;
if(rs_verbose)
printf("RS: starting a %s-component update process\n",
RUPDATE_IS_UPD_MULTI() ? "multi" : "single");
assert(RUPDATE_IS_UPDATING());
assert(rupdate.num_rpupds > 0);
assert(rupdate.num_init_ready_pending == 0);
assert(rupdate.first_rpupd);
assert(rupdate.last_rpupd);
assert(rupdate.curr_rpupd == rupdate.last_rpupd);
rupdate.flags |= RS_INITIALIZING;
/* Cancel the update for the prepare-only services now. */
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, rpupd,
if(UPD_IS_PREPARING_ONLY(rpupd)) {
request_prepare_update_service(rpupd->rp, SEF_LU_STATE_NULL);
}
);
/* Iterate over all the processes scheduled for the update. Update each
* service and initialize the new instance. If VM is part of a
* multi-component live update, initialize VM first.
*/
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, rpupd,
rupdate.curr_rpupd = rpupd;
if(!UPD_IS_PREPARING_ONLY(rpupd)) {
init_ready_pending=1;
r = start_srv_update(rpupd);
if(r != OK) {
return r;
}
if(!RUPDATE_IS_UPD_VM_MULTI() || rpupd == rupdate.vm_rpupd) {
r = complete_srv_update(rpupd);
if(r != OK) {
return r;
}
}
}
);
/* End update if there is nothing more to do. */
if (!init_ready_pending) {
end_update(OK, 0);
return OK;
}
/* Handle multi-component live updates including VM. */
if(RUPDATE_IS_UPD_VM_MULTI()) {
message m;
/* Check VM initialization, assume failure after timeout. */
if (rs_verbose)
printf("RS: waiting for VM to initialize...\n");
r = rs_receive_ticks(VM_PROC_NR, &m, NULL, UPD_INIT_MAXTIME(rupdate.vm_rpupd));
if(r != OK || m.m_type != RS_INIT || m.m_rs_init.result != OK) {
r = (r == OK && m.m_type == RS_INIT ? m.m_rs_init.result : EINTR);
m.m_source = VM_PROC_NR;
m.m_type = RS_INIT;
m.m_rs_init.result = r;
}
do_init_ready(&m);
/* If initialization was successfull, complete the update. */
if(r == OK) {
/* Reply and unblock VM immediately. */
m.m_type = OK;
reply(VM_PROC_NR, NULL, &m);
/* Initialize other services. */
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, rpupd,
if(!UPD_IS_PREPARING_ONLY(rpupd) && rpupd != rupdate.vm_rpupd) {
r = complete_srv_update(rpupd);
if(r != OK) {
return r;
}
}
);
}
}
return OK;
}
/*===========================================================================*
* start_srv_update *
*===========================================================================*/
int start_srv_update(struct rprocupd *rpupd)
{
/* Start updating a single service given its update descriptor. */
struct rproc *old_rp, *new_rp;
int r, sys_upd_flags = 0;
old_rp = rpupd->rp;
new_rp = old_rp->r_new_rp;
assert(old_rp && new_rp);
if(rs_verbose)
printf("RS: %s starting the %s\n", srv_to_string(old_rp), srv_upd_to_string(rpupd));
rupdate.num_init_ready_pending++;
new_rp->r_flags |= RS_INITIALIZING;
new_rp->r_flags |= RS_INIT_PENDING;
if(rpupd->lu_flags & SEF_LU_NOMMAP) {
sys_upd_flags |= SF_VM_NOMMAP;
}
/* Perform the update, skip for RS. */
if(old_rp->r_pub->endpoint != RS_PROC_NR) {
r = update_service(&old_rp, &new_rp, RS_SWAP, sys_upd_flags);
if(r != OK) {
end_update(r, RS_REPLY);
printf("RS: update failed: error %d\n", r);
return r;
}
}
return OK;
}
/*===========================================================================*
* complete_srv_update *
*===========================================================================*/
int complete_srv_update(struct rprocupd *rpupd)
{
/* Complete update of a service given its update descriptor. */
struct rproc *old_rp, *new_rp;
int r;
old_rp = rpupd->rp;
new_rp = old_rp->r_new_rp;
assert(old_rp && new_rp);
if(rs_verbose)
printf("RS: %s completing the %s\n", srv_to_string(old_rp), srv_upd_to_string(rpupd));
new_rp->r_flags &= ~RS_INIT_PENDING;
/* If RS itself is updating, yield control to the new version immediately. */
if(old_rp->r_pub->endpoint == RS_PROC_NR) {
r = init_service(new_rp, SEF_INIT_LU, rpupd->init_flags);
if(r != OK) {
panic("unable to initialize the new RS instance: %d", r);
}
if(rs_verbose)
printf("RS: %s is the new RS instance we'll yield control to\n", srv_to_string(new_rp));
r = sys_privctl(new_rp->r_pub->endpoint, SYS_PRIV_YIELD, NULL);
if(r != OK) {
panic("unable to yield control to the new RS instance: %d", r);
}
/* If we get this far, the new version failed to initialize. Rollback. */
rollback_service(&new_rp, &old_rp);
end_update(ERESTART, RS_REPLY);
printf("RS: update failed: state transfer failed for the new RS instance\n");
return ERESTART;
}
/* Let the new version run. */
r = run_service(new_rp, SEF_INIT_LU, rpupd->init_flags);
if(r != OK) {
/* Something went wrong. Rollback. */
rollback_service(&new_rp, &old_rp);
end_update(r, RS_REPLY);
printf("RS: update failed: error %d\n", r);
return r;
}
return OK;
}
/*===========================================================================*
* abort_update_proc *
*===========================================================================*/
int abort_update_proc(int reason)
{
/* This function is called to abort a scheduled/in-progress update process
* indiscriminately. If the update is in progress, simply pretend the
* current service is causing premature termination of the update.
*/
int is_updating = RUPDATE_IS_UPDATING();
assert(reason != OK);
if(!is_updating && !RUPDATE_IS_UPD_SCHEDULED()) {
return EINVAL;
}
if(rs_verbose)
printf("RS: aborting the %s update process prematurely\n",
is_updating ? "in-progress" : "scheduled");
if(!is_updating) {
rupdate_clear_upds();
return OK;
}
if(rupdate.flags & RS_INITIALIZING) {
/* Pretend the current service under update failed to initialize. */
end_update(reason, RS_REPLY);
}
else {
/* Pretend the current service under update failed to prepare. */
end_update(reason, RS_CANCEL);
}
return OK;
}
/*===========================================================================*
* end_update_curr *
*===========================================================================*/
static void end_update_curr(struct rprocupd *rpupd, int result, int reply_flag)
{
/* Execute the requested action on the current service under update. */
struct rproc *old_rp, *new_rp;
assert(rpupd == rupdate.curr_rpupd);
old_rp = rpupd->rp;
new_rp = old_rp->r_new_rp;
assert(old_rp && new_rp);
if(result != OK && SRV_IS_UPDATING_AND_INITIALIZING(new_rp) && rpupd != rupdate.rs_rpupd) {
/* Rollback in case of failures at initialization time. */
rollback_service(&new_rp, &old_rp);
}
end_srv_update(rpupd, result, reply_flag);
}
/*===========================================================================*
* end_update_before_prepare *
*===========================================================================*/
static void end_update_before_prepare(struct rprocupd *rpupd, int result)
{
/* The service is still waiting for the update. Cleanup the new version and
* keep the old version running.
*/
struct rproc *old_rp, *new_rp;
assert(result != OK);
old_rp = rpupd->rp;
new_rp = old_rp->r_new_rp;
assert(old_rp && new_rp);
cleanup_service(new_rp);
}
/*===========================================================================*
* end_update_prepare_done *
*===========================================================================*/
static void end_update_prepare_done(struct rprocupd *rpupd, int result)
{
/* The service is blocked after preparing for the update. Unblock it
* and cleanup the new version.
*/
assert(!RUPDATE_IS_INITIALIZING());
assert(result != OK);
assert(!(rpupd->rp->r_flags & RS_INITIALIZING));
end_srv_update(rpupd, result, RS_REPLY);
}
/*===========================================================================*
* end_update_initializing *
*===========================================================================*/
static void end_update_initializing(struct rprocupd *rpupd, int result)
{
/* The service is initializing after a live udate. Cleanup the version that
* has to die out and let the other version run.
*/
struct rproc *old_rp, *new_rp;
old_rp = rpupd->rp;
new_rp = old_rp->r_new_rp;
assert(old_rp && new_rp);
assert(SRV_IS_UPDATING_AND_INITIALIZING(new_rp));
if(result != OK && rpupd != rupdate.rs_rpupd) {
/* Rollback in case of failures at initialization time. */
rollback_service(&new_rp, &old_rp);
}
end_srv_update(rpupd, result, RS_REPLY);
}
/*===========================================================================*
* end_update_rev_iter *
*===========================================================================*/
static void end_update_rev_iter(int result, int reply_flag,
struct rprocupd *skip_rpupd, struct rprocupd *only_rpupd)
{
/* End the update for all the requested services. */
struct rprocupd *prev_rpupd, *rpupd;
short is_curr, is_before_curr, is_after_curr;
is_after_curr = 1;
RUPDATE_REV_ITER(rupdate.last_rpupd, prev_rpupd, rpupd,
is_curr = (rupdate.curr_rpupd == rpupd);
is_after_curr = is_after_curr && !is_curr;
if(!UPD_IS_PREPARING_ONLY(rpupd)) {
short is_before_prepare;
short is_prepare_done;
short is_initializing;
is_before_curr = !is_curr && !is_after_curr;
if(RUPDATE_IS_INITIALIZING()) {
is_before_prepare = 0;
is_prepare_done = is_after_curr;
is_initializing = is_before_curr;
}
else {
is_before_prepare = is_after_curr;
is_prepare_done = is_before_curr;
is_initializing = 0;
}
if((!skip_rpupd || rpupd != skip_rpupd) && (!only_rpupd || rpupd == only_rpupd)) {
/* Analyze different cases. */
if(is_curr) {
end_update_curr(rpupd, result, reply_flag);
}
else if(is_before_prepare) {
end_update_before_prepare(rpupd, result);
}
else if(is_prepare_done) {
end_update_prepare_done(rpupd, result);
}
else {
assert(is_initializing);
end_update_initializing(rpupd, result);
}
}
}
);
}
/*===========================================================================*
* end_update_debug *
*===========================================================================*/
void end_update_debug(char *file, int line,
int result, int reply_flag)
{
/* End an in-progress update process. */
struct rprocupd *prev_rpupd, *rpupd, *rpupd_it;
struct rproc *rp, *old_rp, *new_rp;
int i, r, slot_nr;
assert(RUPDATE_IS_UPDATING());
if(rs_verbose)
printf("RS: %s ending the update: result=%d, reply=%d at %s:%d\n",
srv_to_string(rupdate.curr_rpupd->rp), result, (reply_flag==RS_REPLY),
file, line);
/* If the new instance of RS is active and the update failed, ending
* the update couldn't be any easier.
*/
if(result != OK && RUPDATE_IS_RS_INIT_DONE()) {
if(rs_verbose)
printf("RS: update failed, new RS instance will now exit\n");
exit(1);
}
/* Handle prepare-only services first: simply cancel the update. */
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, rpupd,
if(UPD_IS_PREPARING_ONLY(rpupd)) {
if(!RUPDATE_IS_INITIALIZING()) {
request_prepare_update_service(rpupd->rp, SEF_LU_STATE_NULL);
}
rpupd->rp->r_flags &= ~RS_PREPARE_DONE;
}
);
/* Handle all the other services now, VM always last to support rollback. */
end_update_rev_iter(result, reply_flag, rupdate.vm_rpupd, NULL);
if(rupdate.vm_rpupd) {
end_update_rev_iter(result, reply_flag, NULL, rupdate.vm_rpupd);
}
/* End the update and complete initialization in case of success. */
RUPDATE_ITER(rupdate.first_rpupd, prev_rpupd, rpupd,
if(prev_rpupd) {
rupdate_upd_clear(prev_rpupd);
}
if(result == OK && !UPD_IS_PREPARING_ONLY(rpupd)) {
/* The rp pointer points to the new instance in this case. */
new_rp = rpupd->rp;
end_srv_init(new_rp);
}
);
late_reply(rupdate.last_rpupd->rp, result);
rupdate_upd_clear(rupdate.last_rpupd);
RUPDATE_CLEAR();
/* Clear all the old/new endpoints and update flags in the public entries. */
for(slot_nr = 0; slot_nr < NR_SYS_PROCS; slot_nr++) {
rp = &rproc[slot_nr];
rp->r_pub->old_endpoint = NONE;
rp->r_pub->new_endpoint = NONE;
rp->r_pub->sys_flags &= ~(SF_VM_UPDATE|SF_VM_ROLLBACK|SF_VM_NOMMAP);
}
}
/*===========================================================================*
* end_srv_update *
*===========================================================================*/
void end_srv_update(struct rprocupd *rpupd, int result, int reply_flag)
{
/* End the update for the given service. There are two possibilities:
* 1) the update succeeded. In that case, cleanup the old version and mark the
* new version as no longer under update.
* 2) the update failed. In that case, cleanup the new version and mark the old
* version as no longer under update. Eventual late ready to update
* messages (if any) will simply be ignored and the service can
* continue executing. In addition, reset the check timestamp, so that if the
* service has a period, a status request will be forced in the next period.
*/
struct rproc *old_rp, *new_rp, *exiting_rp, *surviving_rp;
struct rproc **rps;
struct rprocpub *rpub;
int nr_rps, i;
old_rp = rpupd->rp;
new_rp = old_rp->r_new_rp;
assert(old_rp && new_rp);
if(result == OK && new_rp->r_pub->endpoint == VM_PROC_NR && RUPDATE_IS_UPD_MULTI()) {
/* VM has already been replied to in case of multi-component live update.
* Send an update cancel message to trigger cleanup.
*/
reply_flag = RS_CANCEL;
}
if(rs_verbose)
printf("RS: ending update from %s to %s with result=%d, reply=%d\n",
srv_to_string(old_rp), srv_to_string(new_rp), result, (reply_flag==RS_REPLY));
/* Decide which version has to die out and which version has to survive. */
surviving_rp = (result == OK ? new_rp : old_rp);
exiting_rp = (result == OK ? old_rp : new_rp);
surviving_rp->r_flags &= ~RS_INITIALIZING;
surviving_rp->r_check_tm = 0;
surviving_rp->r_alive_tm = getticks();
/* Keep track of the surviving process in the update descriptor from now on. */
rpupd->rp = surviving_rp;
/* Unlink the two versions. */
old_rp->r_new_rp = NULL;
new_rp->r_old_rp = NULL;
/* Mark the version that has to survive as no longer updating and
* reply when asked to.
*/
surviving_rp->r_flags &= ~(RS_UPDATING|RS_PREPARE_DONE|RS_INIT_DONE|RS_INIT_PENDING);
if(reply_flag == RS_REPLY) {
message m;
m.m_type = result;
reply(surviving_rp->r_pub->endpoint, surviving_rp, &m);
}
else if(reply_flag == RS_CANCEL) {
if(!(surviving_rp->r_flags & RS_TERMINATED)) {
request_prepare_update_service(surviving_rp, SEF_LU_STATE_NULL);
}
}
/* Cleanup or detach the version that has to die out. */
get_service_instances(exiting_rp, &rps, &nr_rps);
for(i=0;i<nr_rps;i++) {
if(rps[i] == old_rp && (rpupd->lu_flags & SEF_LU_DETACHED)) {
message m;
m.m_type = EDEADEPT;
rps[i]->r_flags |= RS_CLEANUP_DETACH;
cleanup_service(rps[i]);
reply(rps[i]->r_pub->endpoint, rps[i], &m);
}
else {
cleanup_service(rps[i]);
}
}
if(rs_verbose)
printf("RS: %s ended the %s\n", srv_to_string(surviving_rp),
srv_upd_to_string(rpupd));
}