diff options
Diffstat (limited to 'vere/pkg/vere/king.c')
| -rw-r--r-- | vere/pkg/vere/king.c | 1729 |
1 files changed, 1729 insertions, 0 deletions
diff --git a/vere/pkg/vere/king.c b/vere/pkg/vere/king.c new file mode 100644 index 0000000..36a24db --- /dev/null +++ b/vere/pkg/vere/king.c @@ -0,0 +1,1729 @@ +/// @file +/// +/// The main loop of the daemon process. + +#include "vere.h" + +#include "curl/curl.h" +#include "ivory.h" +#include "noun.h" +#include "pace.h" +#include "ur/ur.h" +#include "uv.h" +#include "version.h" + +// It's unclear which file owns this variable, so put it here for now. +u3_host u3_Host; + +u3_king u3_King; + +static const c3_c* ver_hos_c = "https://bootstrap.urbit.org/vere"; + +// stash config flags for worker +// +static c3_w sag_w; + +/* +:: skeleton client->king protocol +:: +|% +:: +doom: daemon command +:: +:: Should require auth to the daemon itself +:: ++$ doom + $% :: boot + :: + :: p: boot procedure + :: q: pill specifier + :: r: path to pier + :: + [%boot p=boot q=pill r=@t] + :: end the daemon + :: + :: XX not implemented + :: + [%exit ~] + :: acquire a pier + :: + :: XX used for restart, may not be right + :: + [%pier p=(unit @t)] + :: admin ship actions + :: + :: XX not implemented + :: + [%root p=ship q=wyrd] + == +:: +boot: boot procedures +:: ++$ boot + $% :: mine a comet + :: + :: p: optionally under a specific star + :: + [%come p=(unit ship)] + :: boot with real keys + :: + :: And perform pre-boot validation, retrieve snapshot, etc. + :: + [%dawn p=seed] + :: boot with fake keys + :: + :: p: identity + :: + [%fake p=ship] + == +:: +pill: boot-sequence ingredients +:: +:: p: jammed pill +:: q: optional %into ovum overriding that of .p +:: ++$ pill [p=@ q=(unit ovum)] +-- +*/ + +void _king_doom(u3_noun doom); + void _king_boot(u3_noun boot); + void _king_come(u3_noun star, u3_noun pill, u3_noun path); + void _king_dawn(u3_noun seed, u3_noun pill, u3_noun path); + void _king_fake(u3_noun ship, u3_noun pill, u3_noun path); + void _king_pier(u3_noun pier); + +static u3_noun _king_get_atom(c3_c* url_c); + +/* _king_defy_fate(): invalid fate +*/ +void +_king_defy_fate() +{ + exit(1); +} + +/* _king_doom(): doom parser +*/ +void +_king_doom(u3_noun doom) +{ + u3_noun load; + void (*next)(u3_noun); + + u3_assert(_(u3a_is_cell(doom))); + u3_assert(_(u3a_is_cat(u3h(doom)))); + + switch ( u3h(doom) ) { + case c3__boot: + next = _king_boot; + break; + case c3__pier: + next = _king_pier; + break; + default: + _king_defy_fate(); + } + + load = u3k(u3t(doom)); + u3z(doom); + next(load); +} + +/* _king_boot(): boot parser +*/ +void +_king_boot(u3_noun bul) +{ + u3_noun boot, pill, path; + void (*next)(u3_noun, u3_noun, u3_noun); + + u3_assert(_(u3a_is_cell(bul))); + u3x_trel(bul, &boot, &pill, &path); + u3_assert(_(u3a_is_cat(u3h(boot)))); + + switch ( u3h(boot) ) { + case c3__fake: { + next = _king_fake; + break; + } + case c3__come: { + next = _king_come; + break; + } + case c3__dawn: { + next = _king_dawn; + break; + } + default: + _king_defy_fate(); + return; + } + + next(u3k(u3t(boot)), u3k(pill), u3k(path)); + u3z(bul); +} + +/* _king_prop(): events from prop arguments +*/ +u3_noun +_king_prop() +{ + u3_noun mor = u3_nul; + while ( 0 != u3_Host.ops_u.vex_u ) { + u3_even* vex_u = u3_Host.ops_u.vex_u; + switch ( vex_u->kin_i ) { + case 1: { // file + u3_atom jam = u3m_file(vex_u->loc_c); + mor = u3nc(u3ke_cue(jam), mor); + } break; + + case 2: { // url + u3l_log("boot: downloading prop %s", vex_u->loc_c); + u3_atom jam = _king_get_atom(vex_u->loc_c); + mor = u3nc(u3ke_cue(jam), mor); + } break; + + case 3: { // name + //NOTE this implementation limits us to max 213 char prop names + c3_c url_c[256]; + snprintf(url_c, 255, + //TODO should maybe respect ops_u.url_c + "https://bootstrap.urbit.org/props/" URBIT_VERSION "/%s.jam", + vex_u->loc_c); + u3l_log("boot: downloading prop %s", url_c); + u3_atom jam = _king_get_atom(url_c); + mor = u3nc(u3ke_cue(jam), mor); + } break; + + default: { + u3l_log("invalid prop source %d", vex_u->kin_i); + exit(1); + } + } + + u3_Host.ops_u.vex_u = vex_u->pre_u; + } + return mor; +} + +/* _king_fake(): boot with fake keys +*/ +void +_king_fake(u3_noun ship, u3_noun pill, u3_noun path) +{ + // XX link properly + // + u3_noun vent = u3nc(c3__fake, u3k(ship)); + u3K.pir_u = u3_pier_boot(sag_w, ship, vent, pill, path, + u3_none, _king_prop()); +} + +/* _king_come(): mine a comet under star (unit) +** +** XX revise to exclude star argument +*/ +void +_king_come(u3_noun star, u3_noun pill, u3_noun path) +{ + _king_dawn(u3_dawn_come(), pill, path); +} + +static void +_king_slog(u3_noun hod) +{ + u3_pier_tank(0, 0, u3k(u3t(hod))); + u3z(hod); +} + +/* _king_dawn(): boot from keys, validating +*/ +void +_king_dawn(u3_noun feed, u3_noun pill, u3_noun path) +{ + // enable ivory slog printfs + // + u3C.slog_f = _king_slog; + + u3_noun ship = ( c3y == u3a_is_cell(u3h(feed)) ) + ? u3h(u3t(feed)) + : u3h(feed); + u3_noun vent = u3_dawn_vent(u3k(ship), u3k(feed)); + // XX link properly + // + u3K.pir_u = u3_pier_boot(sag_w, u3k(ship), vent, pill, path, + feed, _king_prop()); + + // disable ivory slog printfs + // + u3C.slog_f = 0; +} + +/* _king_pier(): pier parser +*/ +void +_king_pier(u3_noun pier) +{ + if ( (c3n == u3du(pier)) || + (c3n == u3ud(u3t(pier))) ) { + u3m_p("daemon: invalid pier", pier); + exit(1); + } + + u3K.pir_u = u3_pier_stay(sag_w, u3k(u3t(pier))); + u3z(pier); +} + +/* king_curl_alloc(): allocate a response buffer for curl +** XX deduplicate with dawn.c +*/ +size_t +king_curl_alloc(void* dat_v, size_t uni_t, size_t mem_t, void* buf_v) +{ + uv_buf_t* buf_u = buf_v; + + size_t siz_t = uni_t * mem_t; + buf_u->base = c3_realloc(buf_u->base, 1 + siz_t + buf_u->len); + + memcpy(buf_u->base + buf_u->len, dat_v, siz_t); + buf_u->len += siz_t; + buf_u->base[buf_u->len] = 0; + + return siz_t; +} + +/* king_curl_bytes(): HTTP GET url_c, produce response body bytes. +*/ +c3_i +king_curl_bytes(c3_c* url_c, c3_w* len_w, c3_y** hun_y, c3_t veb_t, c3_y tri_y) +{ + c3_i ret_i = 0; + CURL *cul_u; + CURLcode res_i; + long cod_i; + c3_y try_y = 0; + uv_buf_t buf_u = uv_buf_init(c3_malloc(1), 0); + + if ( !(cul_u = curl_easy_init()) ) { + u3l_log("failed to initialize libcurl"); + exit(1); + } + + u3K.ssl_curl_f(cul_u); + curl_easy_setopt(cul_u, CURLOPT_URL, url_c); + curl_easy_setopt(cul_u, CURLOPT_WRITEFUNCTION, king_curl_alloc); + curl_easy_setopt(cul_u, CURLOPT_WRITEDATA, (void*)&buf_u); + curl_easy_setopt(cul_u, CURLOPT_SERVER_RESPONSE_TIMEOUT, 30); + + while ( tri_y > try_y ) { + sleep(try_y++); + res_i = curl_easy_perform(cul_u); + curl_easy_getinfo(cul_u, CURLINFO_RESPONSE_CODE, &cod_i); + + if ( CURLE_OK != res_i ) { + if ( veb_t ) { + u3l_log("curl: failed to fetch %s: %s", + url_c, curl_easy_strerror(res_i)); + } + ret_i = -1; + } + else if ( 300 <= cod_i ) { + if ( veb_t ) { + u3l_log("curl: error fetching %s: HTTP %ld", url_c, cod_i); + } + ret_i = -2; + break; + } + else { + *len_w = buf_u.len; + *hun_y = (c3_y*)buf_u.base; + ret_i = 0; + break; + } + } + + curl_easy_cleanup(cul_u); + + return ret_i; +} + +/* _king_get_atom(): HTTP GET url_c, produce response body as atom. +*/ +static u3_noun +_king_get_atom(c3_c* url_c) +{ + c3_w len_w; + c3_y* hun_y; + u3_noun pro; + + if ( king_curl_bytes(url_c, &len_w, &hun_y, 1, 5) ) { + u3_king_bail(); + exit(1); + } + + pro = u3i_bytes(len_w, hun_y); + c3_free(hun_y); + return pro; +} + +/* _king_get_pace(): get "pace" (release channel name). +*/ +static c3_c* +_king_get_pace(void) +{ + struct stat buf_u; + c3_c* pat_c; + c3_w red_w, len_w; + c3_i ret_i, fid_i; + + ret_i = asprintf(&pat_c, "%s/.bin/pace", u3_Host.dir_c); + u3_assert( ret_i > 0 ); + + fid_i = c3_open(pat_c, O_RDONLY, 0644); + + if ( (fid_i < 0) || (fstat(fid_i, &buf_u) < 0) ) { + c3_free(pat_c); + return strdup("live"); + } + + c3_free(pat_c); + + len_w = buf_u.st_size; + pat_c = c3_malloc(len_w + 1); + red_w = read(fid_i, pat_c, len_w); + close(fid_i); + + if ( len_w != red_w ) { + c3_free(pat_c); + u3l_log("unable to read pace file, " + "falling back to default (\"live\")\n"); + return strdup("live"); + } + + pat_c[len_w] = 0; + + while ( len_w-- && isspace(pat_c[len_w]) ) { + pat_c[len_w] = 0; + } + + return pat_c; +} + +/* u3_king_next(): get next vere version string, if it exists. +** return: 0 is success, -1 is no-op (same version), -2 is error +*/ +c3_i +u3_king_next(c3_c* pac_c, c3_c** out_c) +{ + c3_c* ver_c; + c3_c* url_c; + c3_w len_w; + c3_y* hun_y; + c3_i ret_i; + + ret_i = asprintf(&url_c, "%s/%s/%s/next", ver_hos_c, pac_c, URBIT_VERSION); + u3_assert( ret_i > 0 ); + + // no-op if pace is set to once + // + if ( 0 == strcmp(pac_c, "once") ) { + c3_free(url_c); + fprintf(stderr, "vere: pace is set to \"once\", skipping upgrade\r\n"); + fprintf(stderr, "vere: set pace to \"live\" and try again\r\n"); + return -1; + } + + // skip printfs on failed requests (/next is usually not present) + //REVIEW new retry logic means this case will take longer. make retries optional? + // + if ( king_curl_bytes(url_c, &len_w, &hun_y, 0, 5) ) { + c3_free(url_c); + + ret_i = asprintf(&url_c, "%s/%s/last", ver_hos_c, pac_c); + u3_assert( ret_i > 0 ); + + // enable printfs on failed requests (/last must be present) + // XX support channel redirections + // + if ( king_curl_bytes(url_c, &len_w, &hun_y, 1, 5) ) + { + c3_free(url_c); + return -2; + } + } + + c3_free(url_c); + + // null-terminate + // + hun_y = c3_realloc(hun_y, 1 + len_w); + hun_y[len_w] = 0; + c3_c* newline_c; + while ( (newline_c = strrchr((c3_c*)hun_y, '\n')) ) { + *newline_c = 0; + } + + ver_c = (c3_c*)hun_y; + + // XX trim ver_c ? + // + if ( 0 == strcmp(ver_c, URBIT_VERSION) ) { + c3_free(ver_c); + return -1; + } + + *out_c = ver_c; + return 0; +} + +/* _get_cmd_output(): Run a shell command and capture its output. + Exits with an error if the command fails or produces no output. + The 'out_c' parameter should be an array of sufficient length to hold + the command's output, up to a max of len_c characters. +*/ +static void +_get_cmd_output(c3_c *cmd_c, c3_c *out_c, c3_w len_c) +{ + FILE *fp = popen(cmd_c, "r"); + if ( NULL == fp ) { + u3l_log("'%s' failed", cmd_c); + exit(1); + } + + if ( NULL == fgets(out_c, len_c, fp) ) { + u3l_log("'%s' produced no output", cmd_c); + exit(1); + } + + pclose(fp); +} + +/* _arvo_hash(): get a shortened hash of the last git commit + that modified the sys/ directory in arvo. + hax_c must be an array with length >= 11. +*/ +static void +_arvo_hash(c3_c *out_c, c3_c *arv_c) +{ + c3_c cmd_c[2048]; + + sprintf(cmd_c, "git -C %s log -1 HEAD --format=%%H -- sys/", arv_c); + _get_cmd_output(cmd_c, out_c, 11); + + out_c[10] = 0; // end with null-byte +} + +/* _git_pill_url(): produce a URL from which to download a pill + based on the location of an arvo git repository. +*/ +static void +_git_pill_url(c3_c *out_c, c3_c *arv_c) +{ + c3_c hax_c[11]; + + assert(NULL != arv_c); + + if ( 0 != system("which git >> /dev/null") ) { + u3l_log("boot: could not find git executable"); + exit(1); + } + + _arvo_hash(hax_c, arv_c); + sprintf(out_c, "https://bootstrap.urbit.org/git-%s.pill", hax_c); +} + +/* _boothack_pill(): parse CLI pill arguments into +pill specifier +*/ +static u3_noun +_boothack_pill(void) +{ + u3_noun arv = u3_nul; + u3_noun pil; + + if ( 0 != u3_Host.ops_u.pil_c ) { + u3l_log("boot: loading pill %s", u3_Host.ops_u.pil_c); + pil = u3m_file(u3_Host.ops_u.pil_c); + } + else { + c3_c url_c[2048]; + + if ( (c3y == u3_Host.ops_u.git) && + (0 != u3_Host.ops_u.arv_c) ) + { + _git_pill_url(url_c, u3_Host.ops_u.arv_c); + } + else { + u3_assert( 0 != u3_Host.ops_u.url_c ); + strcpy(url_c, u3_Host.ops_u.url_c); + } + + u3l_log("boot: downloading pill %s", url_c); + pil = _king_get_atom(url_c); + } + + if ( 0 != u3_Host.ops_u.arv_c ) { + u3l_log("boot: preparing filesystem from %s", + u3_Host.ops_u.arv_c); + arv = u3nc(u3_nul, u3_unix_initial_into_card(u3_Host.ops_u.arv_c)); + } + + return u3nc(pil, arv); +} + +/* _boothack_key(): parse a private key file or value +*/ +static u3_noun +_boothack_key(u3_noun kef) +{ + u3_noun seed; + u3_weak ship = u3_none; + + { + u3_noun des = u3dc("slaw", c3__uw, u3k(kef)); + + if ( u3_nul == des ) { + c3_c* kef_c = u3r_string(kef); + u3l_log("dawn: invalid private keys: %s", kef_c); + c3_free(kef_c); + exit(1); + } + + // +feed:able:jael: keyfile + // + u3_noun pro = u3m_soft(0, u3ke_cue, u3k(u3t(des))); + if ( u3_blip != u3h(pro) ) { + u3l_log("dawn: unable to cue keyfile"); + exit(1); + } + seed = u3k(u3t(pro)); + u3z(pro); + + // if it's a single seed, we can trivially sanity-check early + // + if ( c3y == u3ud(u3h(seed)) ) { + // local reference, not counted + // + ship = u3h(seed); + } + + u3z(des); + u3z(kef); + } + + if ( 0 != u3_Host.ops_u.who_c ) { + u3_noun woh = u3i_string(u3_Host.ops_u.who_c); + u3_noun whu = u3dc("slaw", 'p', u3k(woh)); + + if ( u3_nul == whu ) { + u3l_log("dawn: invalid ship specified with -w %s", + u3_Host.ops_u.who_c); + exit(1); + } + + if ( (u3_none != ship) && + (c3n == u3r_sing(ship, u3t(whu))) ) + { + u3_noun how = u3dc("scot", 'p', u3k(ship)); + c3_c* how_c = u3r_string(u3k(how)); + u3l_log("dawn: mismatch between -w %s and -K %s", + u3_Host.ops_u.who_c, how_c); + + u3z(how); + c3_free(how_c); + exit(1); + } + + u3z(woh); + u3z(whu); + } + + return seed; +} + +/* _boothack_doom(): parse CLI arguments into $doom +*/ +static u3_noun +_boothack_doom(void) +{ + u3_noun pax = u3i_string(u3_Host.dir_c); + u3_noun bot; + + if ( c3n == u3_Host.ops_u.nuu ) { + return u3nt(c3__pier, u3_nul, pax); + } + else if ( 0 != u3_Host.ops_u.fak_c ) { + u3_noun fak = u3i_string(u3_Host.ops_u.fak_c); + u3_noun whu = u3dc("slaw", 'p', u3k(fak)); + + if ( u3_nul == whu ) { + u3l_log("boot: malformed -F ship %s", u3_Host.ops_u.fak_c); + u3_king_bail(); + } + + bot = u3nc(c3__fake, u3k(u3t(whu))); + + u3z(whu); + u3z(fak); + } + else if ( 0 != u3_Host.ops_u.who_c ) { + u3_noun kef; + + if ( 0 != u3_Host.ops_u.key_c ) { + kef = u3m_file(u3_Host.ops_u.key_c); + + // handle trailing newline + // + { + c3_c* key_c = u3r_string(kef); + c3_w len_w = strlen(key_c); + + if (len_w && (key_c[len_w - 1] == '\n')) { + key_c[len_w - 1] = '\0'; + u3z(kef); + kef = u3i_string(key_c); + } + + c3_free(key_c); + } + } + else if ( 0 != u3_Host.ops_u.gen_c ) { + kef = u3i_string(u3_Host.ops_u.gen_c); + } + else { + u3l_log("boot: must specify a key with -k or -G"); + exit(1); + } + + bot = u3nc(c3__dawn, _boothack_key(kef)); + } + else { + // XX allow parent star to be specified? + // + bot = u3nc(c3__come, u3_nul); + } + + return u3nq(c3__boot, bot, _boothack_pill(), pax); +} + +/* _king_sign_init(): initialize daemon signal handlers +*/ +static void +_king_sign_init(void) +{ + // gracefully shutdown on SIGTERM + // + { + u3_usig* sig_u; + + sig_u = c3_malloc(sizeof(u3_usig)); + uv_signal_init(u3L, &sig_u->sil_u); + + sig_u->num_i = SIGTERM; + sig_u->nex_u = u3_Host.sig_u; + u3_Host.sig_u = sig_u; + } + + // forward SIGINT to worker + // + { + u3_usig* sig_u; + + sig_u = c3_malloc(sizeof(u3_usig)); + uv_signal_init(u3L, &sig_u->sil_u); + + sig_u->num_i = SIGINT; + sig_u->nex_u = u3_Host.sig_u; + u3_Host.sig_u = sig_u; + } + + // inject new dimensions after terminal resize + // + { + u3_usig* sig_u; + + sig_u = c3_malloc(sizeof(u3_usig)); + uv_signal_init(u3L, &sig_u->sil_u); + + sig_u->num_i = SIGWINCH; + sig_u->nex_u = u3_Host.sig_u; + u3_Host.sig_u = sig_u; + } + + // handle SIGINFO (if available) + // +#ifdef SIGINFO + { + u3_usig* sig_u; + + sig_u = c3_malloc(sizeof(u3_usig)); + uv_signal_init(u3L, &sig_u->sil_u); + + sig_u->num_i = SIGINFO; + sig_u->nex_u = u3_Host.sig_u; + u3_Host.sig_u = sig_u; + } +#endif + + // handle SIGUSR1 (fallback for SIGINFO) + // + { + u3_usig* sig_u; + + sig_u = c3_malloc(sizeof(u3_usig)); + uv_signal_init(u3L, &sig_u->sil_u); + + sig_u->num_i = SIGUSR1; + sig_u->nex_u = u3_Host.sig_u; + u3_Host.sig_u = sig_u; + } +} + +/* _king_sign_cb: signal callback. +*/ +static void +_king_sign_cb(uv_signal_t* sil_u, c3_i num_i) +{ + switch ( num_i ) { + default: { + u3l_log("\r\nmysterious signal %d", num_i); + break; + } + + case SIGTERM: { + u3_king_exit(); + break; + } + + case SIGINT: { + u3l_log("\r\ninterrupt"); + u3_term_ef_ctlc(); + + break; + } + + case SIGWINCH: { + u3_term_ef_winc(); + break; + } + + // fallthru if defined + // +#ifdef SIGINFO + case SIGINFO: +#endif + case SIGUSR1: { + u3_king_slog(); + break; + } + } +} + +/* _king_sign_move(): enable daemon signal handlers +*/ +static void +_king_sign_move(void) +{ + u3_usig* sig_u; + + for ( sig_u = u3_Host.sig_u; sig_u; sig_u = sig_u->nex_u ) { + uv_signal_start(&sig_u->sil_u, _king_sign_cb, sig_u->num_i); + } +} + +/* _king_sign_hold(): disable daemon signal handlers +*/ +static void +_king_sign_hold(void) +{ + u3_usig* sig_u; + + for ( sig_u = u3_Host.sig_u; sig_u; sig_u = sig_u->nex_u ) { + uv_signal_stop(&sig_u->sil_u); + } +} + +/* _king_sign_close(): dispose daemon signal handlers +*/ +static void +_king_sign_close(void) +{ + u3_usig* sig_u; + + for ( sig_u = u3_Host.sig_u; sig_u; sig_u = sig_u->nex_u ) { + uv_close((uv_handle_t*)&sig_u->sil_u, (uv_close_cb)free); + } +} +/* _boothack_cb(): setup pier via message as if from client. +*/ +void +_boothack_cb(uv_timer_t* tim_u) +{ + _king_doom(_boothack_doom()); + + // copy binary into pier on boot + // + if ( (c3y == u3_Host.ops_u.nuu) + && (c3y == u3_Host.ops_u.doc) ) + { + u3_king_dock(U3_VERE_PACE); + } +} + +/* _king_loop_init(): stuff that comes before the event loop +*/ +void +_king_loop_init() +{ + // initialize terminal/logging + // + u3_term_log_init(); + + // start signal handlers + // + _king_sign_init(); + _king_sign_move(); + + // async "boothack" + // / + uv_timer_start(&u3K.tim_u, _boothack_cb, 0, 0); +} + +/* _king_loop_exit(): cleanup after event loop +*/ +void +_king_loop_exit() +{ +} + +static void +_king_boot_ivory(void) +{ + c3_d len_d; + c3_y* byt_y; + + if ( u3_Host.ops_u.lit_c ) { + if ( c3n == u3u_mmap_read("lite", u3_Host.ops_u.lit_c, &len_d, &byt_y) ) { + u3l_log("lite: unable to load ivory pill at %s", + u3_Host.ops_u.lit_c); + exit(1); + } + } + else { + len_d = u3_Ivory_pill_len; + byt_y = u3_Ivory_pill; + } + + { + u3_cue_xeno* sil_u = u3s_cue_xeno_init_with(ur_fib27, ur_fib28); + u3_weak pil; + + if ( u3_none == (pil = u3s_cue_xeno_with(sil_u, len_d, byt_y)) ) { + u3l_log("lite: unable to cue ivory pill"); + exit(1); + } + + u3s_cue_xeno_done(sil_u); + + if ( c3n == u3v_boot_lite(pil)) { + u3l_log("lite: boot failed"); + exit(1); + } + } + + if ( u3_Host.ops_u.lit_c ) { + if ( c3n == u3u_munmap(len_d, byt_y) ) { + u3l_log("lite: unable to unmap ivory pill at %s", + u3_Host.ops_u.lit_c); + exit(1); + } + } +} + +/* u3_king_commence(): start the daemon +*/ +void +u3_king_commence() +{ + u3_Host.lup_u = uv_default_loop(); + + // initialize top-level timer + // + uv_timer_init(u3L, &u3K.tim_u); + + // start up a "fast-compile" arvo for internal use only + // (with hashboard and sample-profiling always disabled) + // + sag_w = u3C.wag_w; + u3C.wag_w |= u3o_hashless; + u3C.wag_w &= ~u3o_debug_cpu; + + // wire up signal controls + // + u3C.sign_hold_f = _king_sign_hold; + u3C.sign_move_f = _king_sign_move; + + // Ignore SIGPIPE signals. + { + sigset_t set_s; + sigemptyset(&set_s); + sigaddset(&set_s, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &set_s, NULL); + } + + // boot the ivory pill + // + _king_boot_ivory(); + + // disable core dumps (due to lmdb size) + // + { + struct rlimit rlm; + + getrlimit(RLIMIT_CORE, &rlm); + rlm.rlim_cur = 0; + + if ( 0 != setrlimit(RLIMIT_CORE, &rlm) ) { + u3l_log("king: unable to disable core dumps: %s", strerror(errno)); + exit(1); + } + } + + // run the loop + // + _king_loop_init(); + uv_run(u3L, UV_RUN_DEFAULT); + _king_loop_exit(); + u3m_stop(); +} + +/* u3_king_stub(): get the One Pier for unreconstructed code. +*/ +u3_pier* +u3_king_stub(void) +{ + if ( !u3K.pir_u ) { + u3_assert(!"king: no pier"); + } + else { + return u3K.pir_u; + } +} + +/* _king_forall(): run on all piers +*/ +static void +_king_forall(void (*pir_f)(u3_pier*)) +{ + u3_pier* pir_u = u3K.pir_u; + + while ( pir_u ) { + pir_f(pir_u); + pir_u = pir_u->nex_u; + } +} + +/* u3_king_slog(): print status info. +*/ +void +u3_king_slog(void) +{ + _king_forall(u3_pier_slog); +} + +/* _king_forall_unlink(): run on all piers, unlinking from king. +*/ +static void +_king_forall_unlink(void (*pir_f)(u3_pier*)) +{ + u3_pier* pir_u = u3K.pir_u; + + while ( u3K.pir_u ) { + u3_pier* pir_u = u3K.pir_u; + u3K.pir_u = pir_u->nex_u; + pir_f(pir_u); + } +} + +/* _king_curl_file(): HTTP GET [url_c], write response body to [fil_u]. +*/ +static c3_i +_king_save_file(c3_c* url_c, FILE* fil_u) +{ + c3_i ret_i = 0; + CURL *cul_u; + CURLcode res_i; + long cod_i; + + if ( !(cul_u = curl_easy_init()) ) { + u3l_log("failed to initialize libcurl"); + exit(1); + } + + u3K.ssl_curl_f(cul_u); + curl_easy_setopt(cul_u, CURLOPT_URL, url_c); + curl_easy_setopt(cul_u, CURLOPT_WRITEDATA, (void*)fil_u); + + res_i = curl_easy_perform(cul_u); + curl_easy_getinfo(cul_u, CURLINFO_RESPONSE_CODE, &cod_i); + + // XX retry? + // + if ( CURLE_OK != res_i ) { + u3l_log("curl: failed %s: %s", url_c, curl_easy_strerror(res_i)); + ret_i = -1; + } + if ( 300 <= cod_i ) { + u3l_log("curl: error %s: HTTP %ld", url_c, cod_i); + ret_i = -2; + } + + curl_easy_cleanup(cul_u); + return ret_i; +} + +/* _king_make_pace(): mkdir -p $pier/.bin/[pace] +*/ +static c3_i +_king_make_pace(c3_c* pac_c) +{ + c3_c* bin_c; + c3_i ret_i; + + ret_i = asprintf(&bin_c, "%s/.bin", u3_Host.dir_c); + u3_assert( ret_i > 0 ); + + ret_i = c3_mkdir(bin_c, 0700); + + if ( ret_i && (EEXIST != errno) ) { + fprintf(stderr, "vere: mkdir %s failed: %s\n", bin_c, strerror(errno)); + c3_free(bin_c); + return -1; + } + + c3_free(bin_c); + + ret_i = asprintf(&bin_c, "%s/.bin/%s/", u3_Host.dir_c, pac_c); + u3_assert( ret_i > 0 ); + + // XX asserting wrapper conflicts here (and is bypassed for .urb) + // + ret_i = mkdir(bin_c, 0700); + + if ( ret_i && (EEXIST != errno) ) { + fprintf(stderr, "vere: mkdir %s failed: %s\n", bin_c, strerror(errno)); + c3_free(bin_c); + return -1; + } + + c3_free(bin_c); + return 0; +} + +static c3_i +_king_write_raw(c3_i fid_i, c3_y* buf_y, size_t len_i); + +/* _king_init_pace(): save pace file if not present +*/ +static c3_i +_king_init_pace(c3_c* pac_c) +{ + c3_c* bin_c; + c3_i fid_i, ret_i = asprintf(&bin_c, "%s/.bin/pace", u3_Host.dir_c); + u3_assert( ret_i > 0 ); + + if ( (-1 == (fid_i = open(bin_c, O_WRONLY | O_CREAT | O_EXCL, 0644))) ) { + if ( EEXIST == errno ) { + c3_free(bin_c); + // XX print something here? + // + return 0; + } + else { + u3l_log("dock: init pace (%s): open %s", pac_c, strerror(errno)); + c3_free(bin_c); + return -1; + } + } + + if ( _king_write_raw(fid_i, (c3_y*)pac_c, strlen(pac_c)) ) { + u3l_log("dock: init pace (%s): write %s", pac_c, strerror(errno)); + close(fid_i); + c3_free(bin_c); + return -1; + } + // XX sync first? + // + else if ( close(fid_i) ) { + u3l_log("dock: init pace (%s): close %s", pac_c, strerror(errno)); + c3_free(bin_c); + return 1; + } + + u3l_log("dock: pace (%s): configured at %s/.bin/pace", + pac_c, u3_Host.dir_c); + + return 0; +} + +/* _king_link_run(): ln [bin_c] $pier/.run +*/ +static c3_i +_king_link_run(c3_c* bin_c) +{ + c3_c* lin_c; + c3_i ret_i; + + ret_i = asprintf(&lin_c, "%s/%s", u3_Host.dir_c, U3_BIN_ALIAS); + u3_assert( ret_i > 0 ); + + ret_i = unlink(lin_c); + + if ( ret_i && (ENOENT != errno) ) { + fprintf(stderr, "vere: unlink %s failed: %s\n", lin_c, strerror(errno)); + c3_free(lin_c); + return -1; + } + + ret_i = link(bin_c, lin_c); + + if ( ret_i ) { + fprintf(stderr, "vere: link %s -> %s failed: %s\n", + lin_c, bin_c, strerror(errno)); + c3_free(lin_c); + return -1; + } + + c3_free(lin_c); + return 0; +} + +/* u3_king_vere(): download binary as specified. +*/ +c3_i +u3_king_vere(c3_c* pac_c, // pace + c3_c* ver_c, // version + c3_c* arc_c, // architecture + c3_c* dir_c, // output directory + c3_t lin_t) // link to $pier/.run +{ + c3_c* bin_c; + c3_c* url_c; + FILE* fil_u; + c3_i fid_i, ret_i; + + ret_i = asprintf(&bin_c, "%s/vere-v%s-%s", dir_c, ver_c, arc_c); + u3_assert( ret_i > 0 ); + + if ( (-1 == (fid_i = open(bin_c, O_WRONLY | O_CREAT | O_EXCL, 0755))) + || !(fil_u = fdopen(fid_i, "wb")) ) + { + if ( EEXIST == errno ) { + u3l_log("already installed"); + c3_free(bin_c); + return 0; + } + else { + u3l_log("unable to open %s: %s", bin_c, strerror(errno)); + c3_free(bin_c); + return -1; + } + } + + ret_i = asprintf(&url_c, "%s/%s/v%s/vere-v%s-%s", + ver_hos_c, pac_c, ver_c, ver_c, arc_c); + u3_assert( ret_i > 0 ); + + if ( (ret_i = _king_save_file(url_c, fil_u)) ) { + u3l_log("unable to save %s to %s: %d", url_c, bin_c, ret_i); + c3_free(url_c); + fclose(fil_u); + unlink(bin_c); + c3_free(bin_c); + return -1; // XX + } + + // XX sync unnecessary here? + // + if ( fflush(fil_u) || c3_sync(fid_i) ) { + fprintf(stderr, "vere: sync %s failed: %s\n", bin_c, strerror(errno)); + c3_free(url_c); + fclose(fil_u); + unlink(bin_c); + c3_free(bin_c); + return -1; + } + + fclose(fil_u); + + // XX if link fails wat do? + // XX set via cli option + // + if ( lin_t ) { + if ( _king_link_run(bin_c) ) { + fprintf(stderr, "vere: link %s/%s failed\n", u3_Host.dir_c, U3_BIN_ALIAS); + c3_free(url_c); + c3_free(bin_c); + return -1; + } + } + + u3l_log("vere: saved to %s", bin_c); + + c3_free(url_c); + c3_free(bin_c); + + return 0; +} + +/* _king_do_upgrade(): get arch-appropriate binary at [ver_c]. +*/ +static void +_king_do_upgrade(c3_c* pac_c, c3_c* ver_c) +{ + c3_c* dir_c; + c3_c* arc_c; + +#ifdef U3_OS_ARCH + arc_c = U3_OS_ARCH; +#else + if ( u3_Host.arc_c ) { + arc_c = u3_Host.arc_c; + } + else { + u3l_log("vere: --arch required"); + return; + } +#endif + + if ( _king_make_pace(pac_c) ) { + u3l_log("vere: unable to make pace (%s) directory in pier", pac_c); + u3_king_bail(); + exit(1); + } + + { + c3_i ret_i = asprintf(&dir_c, "%s/.bin/%s", u3_Host.dir_c, pac_c); + u3_assert( ret_i > 0 ); + } + + // XX get link option + // + if ( u3_king_vere(pac_c, ver_c, arc_c, dir_c, 1) ) { + u3l_log("vere: upgrade failed"); + u3_king_bail(); + exit(1); + } + + c3_free(dir_c); + u3l_log("vere: upgrade succeeded"); + // XX print restart instructions +} + +/* _king_read_raw: read (up to) [len_i] from [fid_i] to [buf_y] +*/ +static ssize_t +_king_read_raw(c3_i fid_i, c3_y* buf_y, size_t len_i) +{ + ssize_t ret_i; + + do { + ret_i = read(fid_i, buf_y, len_i); + } + while ( (ret_i < 0) && (errno == EINTR) ); + + return ret_i; +} + +/* _king_read_raw: write [len_i] from [buf_y] to [fid_i]. +*/ +static c3_i +_king_write_raw(c3_i fid_i, c3_y* buf_y, size_t len_i) +{ + ssize_t ret_i; + + while ( len_i ) { + + do { + ret_i = write(fid_i, buf_y, len_i); + } + while ( (ret_i < 0) && (errno == EINTR) ); + + if ( ret_i < 0 ) { + return -1; + } + else { + len_i -= ret_i; + buf_y += ret_i; + } + } + + return 0; +} + +static c3_i +_king_copy_raw(c3_i src_i, c3_i dst_i, c3_y* buf_y, size_t pag_i) +{ + ssize_t red_i; + + do { + if ( 0 > (red_i = _king_read_raw(src_i, buf_y, pag_i)) ) { + return -1; + } + + if ( _king_write_raw(dst_i, buf_y, (size_t)red_i) ) { + return -1; + } + } + while ( red_i ); + + return 0; +} + +static c3_i +_king_copy_file(c3_c* src_c, c3_c* dst_c) +{ +#if defined(U3_OS_osx) + if ( !clonefile(src_c, dst_c, 0) ) { + return 0; + } + // fallthru to copying bytes on some errors + // + else if ( (ENOTSUP != errno) && (EXDEV != errno) ) { + return -1; + } +#endif + + { + c3_i src_i, dst_i, ret_i = 0, err_i = 0; + + if ( -1 == (src_i = open(src_c, O_RDONLY, 0644)) ) { + err_i = errno; + ret_i = -1; + goto done1; + } + + if ( -1 == (dst_i = open(dst_c, O_RDWR | O_CREAT, 0755)) ) { + err_i = errno; + ret_i = -1; + goto done2; + } + + // XX try clone_file_range ? + // +#if defined(U3_OS_linux) + #if defined(FICLONE) + if ( !ioctl(dst_i, FICLONE, src_i) ) { + ret_i = 0; + goto done3; + } + // fallthru to copying bytes on some errors + // + else if ( (EOPNOTSUPP != errno) && (EXDEV != errno) ) { + err_i = errno; + ret_i = -1; + goto done3; + } + #endif + + { + off_t off_i = 0; + ssize_t sen_i; + size_t len_i; + { + struct stat sat_u; + if ( -1 == fstat(src_i, &sat_u) ) { + err_i = errno; + ret_i = -1; + goto done3; + } + len_i = sat_u.st_size; + } + + do { + // XX fallback on any errors? + // + if ( 0 > (sen_i = sendfile(dst_i, src_i, &off_i, len_i)) ) { + err_i = errno; + ret_i = -1; + goto done3; + } + + len_i -= off_i; + } + while ( len_i ); + + ret_i = 0; + goto done3; + } +#elif defined(U3_OS_osx) + if ( !fcopyfile(src_i, dst_i, NULL, COPYFILE_ALL) ) { + ret_i = 0; + goto done3; + } + + // XX fallback on any errors? + // +#endif + + { + size_t pag_i = 1 << 14;; + c3_y* buf_y = c3_malloc(pag_i); + ret_i = _king_copy_raw(src_i, dst_i, buf_y, pag_i); + err_i = errno; + c3_free(buf_y); + } + +done3: + close(dst_i); +done2: + close(src_i); +done1: + errno = err_i; + return ret_i; + } +} + +/* _king_copy_vere(): copy current binary into $pier/.bin (COW if possible) +*/ +static c3_i +_king_copy_vere(c3_c* pac_c, c3_c* ver_c, c3_c* arc_c, c3_t lin_t) +{ + c3_c* bin_c; + c3_i ret_i; + + if ( _king_make_pace(pac_c) ) { + return -1; // XX + } + + ret_i = asprintf(&bin_c, "%s/.bin/%s/vere-v%s-%s", + u3_Host.dir_c, pac_c, ver_c, arc_c); + u3_assert( ret_i > 0 ); + + ret_i = _king_copy_file(u3_Host.dem_c, bin_c); + + if ( ret_i ) { + fprintf(stderr, "vere: copy %s -> %s failed: %s\r\n", + u3_Host.dem_c, bin_c, strerror(errno)); + c3_free(bin_c); + return -1; + } + + // XX option + // + if ( lin_t ) { + if ( _king_link_run(bin_c) ) { + fprintf(stderr, "vere: link %s/%s failed\n", u3_Host.dir_c, U3_BIN_ALIAS); + c3_free(bin_c); + return -1; + } + } + + c3_free(bin_c); + return 0; +} + +/* u3_king_dock(): copy binary into pier on boot. +*/ +void +u3_king_dock(c3_c* pac_c) +{ + c3_c* arc_c = "unknown"; + +#ifdef U3_OS_ARCH + arc_c = U3_OS_ARCH; +#endif + + // XX get link option + // + if ( _king_copy_vere(pac_c, URBIT_VERSION, arc_c, 1) ) { + u3l_log("vere: binary copy failed"); + u3_king_bail(); + exit(1); + } + else { + // NB: failure ignored + // + _king_init_pace(pac_c); + u3l_log("vere: binary copy succeeded"); + // XX print restart instructions + } +} + +/* _king_done_cb(): +*/ +static void +_king_done_cb(uv_handle_t* han_u) +{ + if( UV_EBUSY == uv_loop_close(u3L) ) { + // XX uncomment to debug + // + // fprintf(stderr, "\r\nking: open libuv handles\r\n"); + // uv_print_all_handles(u3L, stderr); + // fprintf(stderr, "\r\nking: force shutdown\r\n"); + + uv_stop(u3L); + } +} + +/* u3_king_done(): all piers closed. s/b callback +*/ +void +u3_king_done(void) +{ + uv_handle_t* han_u = (uv_handle_t*)&u3K.tim_u; + + if ( u3_Host.xit_i ) { + if ( c3y == u3_Host.nex_o ) { + u3l_log("vere: upgrade failed"); + } + else if ( c3y == u3_Host.pep_o ) { + u3l_log("vere: prep for upgrade failed"); + } + } + else { + // get next binary + // + if ( c3y == u3_Host.nex_o ) { + c3_c* pac_c; + c3_c* ver_c; + + // hack to ensure we only try once + // + u3_Host.nex_o = c3n; + + pac_c = _king_get_pace(); + + switch ( u3_king_next(pac_c, &ver_c) ) { + case -2: { + u3l_log("vere: unable to check for next version"); + } break; + + case -1: { + u3l_log("vere: up to date"); + } break; + + case 0: { + u3l_log("vere: next (%%%s): %s", pac_c, ver_c); + _king_do_upgrade(pac_c, ver_c); + c3_free(ver_c); + } break; + + default: u3_assert(0); + } + + c3_free(pac_c); + } + else if ( c3y == u3_Host.pep_o ) { + u3l_log("vere: ready for upgrade"); + } + } + + // XX hack, if pier's are still linked, we're not actually done + // + if ( !u3K.pir_u && !uv_is_closing(han_u) ) { + uv_close((uv_handle_t*)&u3K.tim_u, _king_done_cb); + _king_sign_close(); + + u3_term_log_exit(); + fflush(stdout); + } + + // XX remove move + // + exit(u3_Host.xit_i); +} + +/* u3_king_exit(): shutdown gracefully +*/ +void +u3_king_exit(void) +{ + _king_forall(u3_pier_exit); +} + +/* u3_king_bail(): immediately shutdown. +*/ +void +u3_king_bail(void) +{ + u3_Host.xit_i = 1; + _king_forall_unlink(u3_pier_bail); + _king_loop_exit(); + u3_king_done(); + exit(u3_Host.xit_i); +} + +/* u3_king_grab(): gc the daemon +*/ +void +u3_king_grab(void* vod_p) +{ + FILE* fil_u; + + u3_assert( u3R == &(u3H->rod_u) ); + +#ifdef U3_MEMORY_LOG + { + // XX date will not match up with that of the worker + // + u3_noun wen = u3dc("scot", c3__da, u3k(u3A->now)); + c3_c* wen_c = u3r_string(wen); + + c3_c nam_c[2048]; + snprintf(nam_c, 2048, "%s/.urb/put/mass", u3_king_stub()->pax_c); + + struct stat st; + if ( -1 == stat(nam_c, &st) ) { + c3_mkdir(nam_c, 0700); + } + + c3_c man_c[2048]; + snprintf(man_c, 2048, "%s/%s-daemon.txt", nam_c, wen_c); + + fil_u = c3_fopen(man_c, "w"); + fprintf(fil_u, "%s\r\n", wen_c); + + c3_free(wen_c); + u3z(wen); + } +#else + { + fil_u = u3_term_io_hija(); + fprintf(fil_u, "measuring daemon:\r\n"); + } +#endif + + u3m_quac** all_u = c3_malloc(sizeof(*all_u)*6); + + u3m_quac** var_u = u3m_mark(); + all_u[0] = var_u[0]; + all_u[1] = var_u[1]; + all_u[2] = var_u[2]; + all_u[3] = var_u[3]; + c3_free(var_u); + + c3_w tot_w = all_u[0]->siz_w + all_u[1]->siz_w + + all_u[2]->siz_w + all_u[3]->siz_w; + + all_u[4] = c3_calloc(sizeof(*all_u[4])); + all_u[4]->nam_c = "total marked"; + all_u[4]->siz_w = tot_w; + + all_u[5] = c3_calloc(sizeof(*all_u[5])); + all_u[5]->nam_c = "sweep"; + all_u[5]->siz_w = u3a_sweep(); + + for ( c3_w i_w = 0; i_w < 6; i_w++ ) { + u3a_print_quac(fil_u, 0, all_u[i_w]); + u3a_quac_free(all_u[i_w]); + } + + c3_free(all_u); + +#ifdef U3_MEMORY_LOG + { + fclose(fil_u); + } +#else + { + u3_term_io_loja(0, fil_u); + } +#endif +} |
