Author: Andy Green Date: Tue May 13 20:00:31 2025 +0100 sai-power: add server side diff --git a/CMakeLists.txt b/CMakeLists.txt index fe0b18f..dbd61e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -155,6 +155,8 @@ if (requirements) if (SAI_BUILDER) add_subdirectory(src/builder) add_subdirectory(src/resource) + add_subdirectory(src/power) + if (NOT MSVC AND NOT WIN32 AND NOT (${CMAKE_SYSTEM_NAME} MATCHES "SunOS")) add_subdirectory(src/device) add_subdirectory(src/expect) diff --git a/src/builder/b-conf.c b/src/builder/b-conf.c index ef87972..a15b864 100644 --- a/src/builder/b-conf.c +++ b/src/builder/b-conf.c @@ -36,7 +36,12 @@ static const char * const paths_global[] = { "metrics_uri", "metrics_path", "metrics_secret", - "power-off" + "sai-power", + "power-on.type", + "power-on.url", + "power-on.mac", + "power-off.type", + "power-off.url" }; enum enum_paths_global { @@ -46,7 +51,12 @@ enum enum_paths_global { LEJPM_METRICS_URI, LEJPM_METRICS_PATH, LEJPM_METRICS_SECRET, - LEJPM_POWER_OFF, + LEJPM_SAI_POWER, + LEJPM_POWER_ON_TYPE, + LEJPM_POWER_ON_URL, + LEJPM_POWER_ON_MAC, + LEJPM_POWER_OFF_TYPE, + LEJPM_POWER_OFF_URL }; /* platform-related part */ @@ -287,8 +297,20 @@ saib_conf_global_cb(struct lejp_ctx *ctx, char reason) pp = &a->builder->metrics_secret; break; - case LEJPM_POWER_OFF: - pp = &a->builder->power_off; + case LEJPM_POWER_ON_TYPE: + pp = &a->builder->power_on_type; + break; + case LEJPM_POWER_ON_URL: + pp = &a->builder->power_on_url; + break; + case LEJPM_POWER_ON_MAC: + pp = &a->builder->power_on_mac; + break; + case LEJPM_POWER_OFF_TYPE: + pp = &a->builder->power_off_type; + break; + case LEJPM_POWER_OFF_URL: + pp = &a->builder->power_off_url; break; default: diff --git a/src/builder/b-private.h b/src/builder/b-private.h index c481c15..f2fee9e 100644 --- a/src/builder/b-private.h +++ b/src/builder/b-private.h @@ -170,7 +170,12 @@ struct sai_builder { const char *metrics_path; const char *metrics_secret; - const char *power_off; + + const char *power_off_type; + const char *power_off_url; + const char *power_on_type; + const char *power_on_url; + const char *power_on_mac; const char *home; /* home dir, usually /sai/home */ const char *perms; /* user:group */ diff --git a/src/builder/b-sai.c b/src/builder/b-sai.c index 1287bfe..8da2cc4 100644 --- a/src/builder/b-sai.c +++ b/src/builder/b-sai.c @@ -28,6 +28,8 @@ #include #include #include +#include +#include #if defined(__linux__) #include @@ -581,6 +583,13 @@ int main(int argc, const char **argv) "Copyright (C) 2019-2020 Andy Green \n"); lwsl_user(" sai-builder [-c ]\n"); + lwsl_notice("%s: sai-power: %s %s %s %s %s\n", + __func__, builder.power_on_type, + builder.power_on_url, + builder.power_on_mac, + builder.power_off_type, + builder.power_off_url); + memset(&info, 0, sizeof info); info.port = CONTEXT_PORT_NO_LISTEN; info.pprotocols = pprotocols; @@ -631,9 +640,12 @@ int main(int argc, const char **argv) goto bail; } - if (!strcmp(builder.power_off, "suspend")) { + if (!strcmp(builder.power_off_type, "suspend")) { struct lws_spawn_piped_info info; - const char * const ea[] = { argv[0], "-s", NULL }; + char rpath[PATH_MAX]; + const char * const ea[] = { rpath, "-s", NULL }; + + realpath(argv[0], rpath); memset(&info, 0, sizeof(info)); memset(&builder.suspend_nspawn, 0, sizeof(builder.suspend_nspawn)); @@ -683,7 +695,7 @@ int main(int argc, const char **argv) bail: - if (!strcmp(builder.power_off, "suspend") && lsp_suspender) { + if (!strcmp(builder.power_off_type, "suspend") && lsp_suspender) { uint8_t te = 2; /* @@ -725,7 +737,7 @@ bail: saib_config_destroy(&builder); - if (!strcmp(builder.power_off, "suspend")) + if (!strcmp(builder.power_off_type, "suspend")) lws_sul_cancel(&builder.sul_idle); /* diff --git a/src/builder/b-task.c b/src/builder/b-task.c index 3b42437..aa9c659 100644 --- a/src/builder/b-task.c +++ b/src/builder/b-task.c @@ -165,7 +165,7 @@ saib_task_destroy(struct sai_nspawn *ns) ns->sp->ongoing, ns->sp->ongoing - 1); ns->sp->ongoing--; - if (!strcmp(builder.power_off, "suspend") && !ns->sp->ongoing) { + if (!strcmp(builder.power_off_type, "suspend") && !ns->sp->ongoing) { int m = 0; /* @@ -718,7 +718,7 @@ saib_ws_json_rx_builder(struct sai_plat_server *spm, const void *in, size_t len) sp->ongoing++; ns->task->told_ongoing = 1; - if (!strcmp(builder.power_off, "suspend")) { + if (!strcmp(builder.power_off_type, "suspend")) { /* we're busy, we're not in the mood for suspending */ lwsl_notice("%s: cancelling suspend grace time\n", __func__); lws_sul_cancel(&ns->builder->sul_idle); diff --git a/src/power/CMakeLists.txt b/src/power/CMakeLists.txt new file mode 100644 index 0000000..176494d --- /dev/null +++ b/src/power/CMakeLists.txt @@ -0,0 +1,53 @@ +set(SUB "sai-power") +set(CPACK_DEBIAN_BUILDER_PACKAGE_NAME "sai-power") + +set(SRCS + p-sai.c + p-conf.c + p-comms.c +) + +set(requirements 1) +require_lws_config(LWS_WITH_CLIENT 1 requirements) +require_lws_config(LWS_WITH_SPAWN 1 requirements) +require_lws_config(LWS_WITH_STRUCT_JSON 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_DIR 1 requirements) + +if (requirements) + add_executable(${SUB} ${SRCS}) + if (APPLE) + set_property(TARGET ${SUB} PROPERTY MACOSX_RPATH YES) + endif() + + if (SAI_LWS_INC_PATH) + target_include_directories(${SUB} PRIVATE ${SAI_LWS_INC_PATH}) + endif() + + if (HAS_LIBCAP) + find_library(CAP_LIB_PATH NAMES "cap") + endif() + + target_link_libraries(${SUB} websockets ${SAI_LWS_LIB_PATH}) + + message("LWS_OPENSSL_LIBRARIES ${SUB} '${LWS_OPENSSL_LIBRARIES}'") + if (LWS_OPENSSL_LIBRARIES) + target_link_libraries(${SUB} ${LWS_OPENSSL_LIBRARIES}) + endif() + + if (SAI_EXT_PTHREAD_LIBRARIES) + target_link_libraries(${SUB} ${SAI_EXT_PTHREAD_LIBRARIES}) + endif() + if (HAS_LIBCAP) + target_link_libraries(${SUB} ${CAP_LIB_PATH}) + endif() + + if (MSVC OR WIN32) + target_link_libraries(${SUB} ws2_32.lib userenv.lib psapi.lib iphlpapi.lib) + endif() + + install(TARGETS "sai-power" + RUNTIME DESTINATION "${BIN_DIR}" COMPONENT power) + +endif(requirements) +include(CPack) diff --git a/src/power/p-comms.c b/src/power/p-comms.c new file mode 100644 index 0000000..ee72344 --- /dev/null +++ b/src/power/p-comms.c @@ -0,0 +1,165 @@ +/* + * sai-power com-warmcat-sai client protocol implementation + * + * Copyright (C) 2019 - 2025 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include +#include + +#include "p-private.h" + +#include "../common/struct-metadata.c" + +LWS_SS_USER_TYPEDEF + char payload[200]; + size_t size; + size_t pos; +} saip_server_link_t; + +static lws_ss_state_return_t +saip_m_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + saip_server_link_t *pss = (saip_server_link_t *)userobj; + saip_server_t *sps = (saip_server_t *)lws_ss_opaque_from_user(pss); + + lwsl_notice("%s: len %d, flags: %d\n", __func__, (int)len, flags); + lwsl_hexdump_notice(buf, len); + + (void)sps; + +// if (saip_ws_json_rx_power(sps, buf, len)) +// return 1; + + return 0; +} + + +static int +cleanup_on_ss_destroy(struct lws_dll2 *d, void *user) +{ + saip_server_link_t *pss = (saip_server_link_t *)user; + saip_server_t *sps = (saip_server_t *)lws_ss_opaque_from_user(pss); + + (void)sps; + + + return 0; +} + +static int +cleanup_on_ss_disconnect(struct lws_dll2 *d, void *user) +{ + return 0; +} + +static lws_ss_state_return_t +saip_m_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + saip_server_link_t *pss = (saip_server_link_t *)userobj; + saip_server_t *sps = (saip_server_t *)lws_ss_opaque_from_user(pss); + const char *pq; + int n; + + lwsl_user("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state), + (unsigned int)ack); + + switch (state) { + + case LWSSSCS_CREATING: + + lwsl_notice("%s: binding ss to %p %s\n", __func__, sps, sps->url); + + if (lws_ss_set_metadata(sps->ss, "url", sps->url, strlen(sps->url))) + lwsl_warn("%s: unable to set metadata\n", __func__); + + pq = sps->url; + while (*pq && (pq[0] != '/' || pq[1] != '/')) + pq++; + + if (*pq) { + n = 0; + pq += 2; + while (pq[n] && pq[n] != '/') + n++; + } else { + pq = sps->url; + n = (int)strlen(pq); + } + + sps->name = sps->url + strlen(sps->url) + 1; + memcpy((char *)sps->name, pq, (unsigned int)n); + ((char *)sps->name)[n] = '\0'; + + while (strchr(sps->name, '.')) + *strchr(sps->name, '.') = '_'; + while (strchr(sps->name, '/')) + *strchr(sps->name, '/') = '_'; + + break; + + case LWSSSCS_DESTROYING: + + /* + * If the logical SS itself is going down, every platform that + * used us to connect to their server and has nspawns are also + * going down + */ + lws_dll2_foreach_safe(&power.sai_server_owner, sps, + cleanup_on_ss_destroy); + + break; + + case LWSSSCS_CONNECTED: + lwsl_user("%s: CONNECTED: %p\n", __func__, sps->ss); + return lws_ss_request_tx(sps->ss); + + case LWSSSCS_DISCONNECTED: + /* + * clean up any ongoing spawns related to this connection + */ + + lwsl_user("%s: DISCONNECTED\n", __func__); + lws_dll2_foreach_safe(&power.sai_server_owner, sps, + cleanup_on_ss_disconnect); + break; + + case LWSSSCS_ALL_RETRIES_FAILED: + lwsl_user("%s: LWSSSCS_ALL_RETRIES_FAILED\n", __func__); + return lws_ss_request_tx(sps->ss); + + case LWSSSCS_QOS_ACK_REMOTE: + lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__); + break; + + default: + break; + } + + return LWSSSSRET_OK; +} + +LWS_SS_INFO("power_server_link", saip_server_link_t) + .rx = saip_m_rx, +// .tx = saip_m_tx, + .state = saip_m_state, + .user_alloc = sizeof(saip_server_link_t), + .streamtype = "sai_power" +}; diff --git a/src/power/p-conf.c b/src/power/p-conf.c new file mode 100644 index 0000000..6411158 --- /dev/null +++ b/src/power/p-conf.c @@ -0,0 +1,220 @@ +/* + * sai-power conf.c + * + * Copyright (C) 2019 - 2025 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include "p-private.h" + +/* global part */ + +static const char * const paths_global[] = { + "perms", + "servers[].url", + "servers[].platforms[].name", + "servers[].platforms[].power-on.type", + "servers[].platforms[].power-on.mac", + "servers[].platforms[].power-on.url", + "servers[].platforms[].power-on", + "servers[].platforms[].power-off.type", + "servers[].platforms[].power-off.url", + "servers[].platforms[].power-off", + "servers[].platforms[]", + "servers[]" +}; + +enum enum_paths_global { + LEJPM_PERMS, + LEJPM_SERVERS_URL, + LEJPM_SERVERS_PLATFORMS_NAME, + LEJPM_SERVERS_PLATFORMS_POWER_ON_TYPE, + LEJPM_SERVERS_PLATFORMS_POWER_ON_MAC, + LEJPM_SERVERS_PLATFORMS_POWER_ON_URL, + LEJPM_SERVERS_PLATFORMS_POWER_ON, + LEJPM_SERVERS_PLATFORMS_POWER_OFF_TYPE, + LEJPM_SERVERS_PLATFORMS_POWER_OFF_URL, + LEJPM_SERVERS_PLATFORMS_POWER_OFF, + LEJPM_SERVERS_PLATFORMS, + LEJPM_SERVERS +}; + +static signed char +saip_conf_global_cb(struct lejp_ctx *ctx, char reason) +{ + struct jpargs *a = (struct jpargs *)ctx->user; + + const char **pp = NULL; +#if 0 +// int n; + + lwsl_notice("%s: reason: %d, path: %s, match %d\n", __func__, + reason, ctx->path, ctx->path_match); +// for (n = 0; n < ctx->wildcount; n++) +// lwsl_notice(" %d\n", ctx->wild[n]); +#endif + + + if (reason == LEJPCB_OBJECT_START) { + switch (ctx->path_match - 1) { + + case LEJPM_SERVERS: + /* + * Create the saip_server object + */ + + a->sai_server = lwsac_use_zero(&a->power->ac_conf_head, + sizeof(*a->sai_server), 4096); + if (!a->sai_server) + return -1; + + lwsl_notice("%s: adding server %p\n", __func__, a->sai_server); + + lws_dll2_add_tail(&a->sai_server->list, + &a->power->sai_server_owner); + break; + + + case LEJPM_SERVERS_PLATFORMS: + /* + * Create the saip_platform object and bind to the server + */ + a->sai_server_plat = lwsac_use_zero(&a->power->ac_conf_head, + sizeof(*a->sai_server_plat), 4096); + if (!a->sai_server_plat) + return -1; + + lws_dll2_add_tail(&a->sai_server_plat->list, + &a->sai_server->sai_plat_owner); + break; + + default: + return 0; + } + } + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + if (reason != LEJPCB_VAL_STR_END) + return 0; + + /* only the end part of the string, where we know the length */ + + switch (ctx->path_match - 1) { + + case LEJPM_PERMS: + pp = &a->power->perms; + break; + + case LEJPM_SERVERS_URL: + pp = &a->sai_server->url; + lwsl_user("%s: server url %.*s\n", __func__, ctx->npos, ctx->buf); + break; + + case LEJPM_SERVERS_PLATFORMS_NAME: + pp = &a->sai_server_plat->name; + break; + + case LEJPM_SERVERS_PLATFORMS_POWER_ON_TYPE: + pp = &a->sai_server_plat->power_on_type; + break; + + case LEJPM_SERVERS_PLATFORMS_POWER_ON_MAC: + pp = &a->sai_server_plat->power_on_mac; + break; + + case LEJPM_SERVERS_PLATFORMS_POWER_ON_URL: + pp = &a->sai_server_plat->power_on_url; + break; + + case LEJPM_SERVERS_PLATFORMS_POWER_OFF_TYPE: + pp = &a->sai_server_plat->power_off_type; + break; + + case LEJPM_SERVERS_PLATFORMS_POWER_OFF_URL: + pp = &a->sai_server_plat->power_off_url; + break; + + default: + return 0; + } + + *pp = lwsac_use(&a->power->ac_conf_head, ctx->npos + 1u, 512); + if (!*pp) + return 1; + + memcpy((char *)(*pp), ctx->buf, ctx->npos); + ((char *)(*pp))[ctx->npos] = '\0'; + + return 0; +} + +int +saip_config_global(struct sai_power *power, const char *d) +{ + unsigned char buf[128]; + struct lejp_ctx ctx; + int n, m, fd; + struct jpargs a; + + memset(&a, 0, sizeof(a)); + a.power = power; + +#if defined(WIN32) + lws_snprintf((char *)buf, sizeof(buf) - 1, "%s\\conf", d); +#else + lws_snprintf((char *)buf, sizeof(buf) - 1, "%s/conf", d); +#endif + + fd = lws_open((char *)buf, O_RDONLY); + if (fd < 0) { + lwsl_err("Cannot open %s\n", (char *)buf); + return 2; + } + lwsl_info("%s: %s\n", __func__, (char *)buf); + lejp_construct(&ctx, saip_conf_global_cb, &a, + paths_global, LWS_ARRAY_SIZE(paths_global)); + + do { + n = (int)read(fd, buf, sizeof(buf)); + if (!n) + break; + + m = lejp_parse(&ctx, buf, n); + } while (m == LEJP_CONTINUE); + + close(fd); + n = (int)ctx.line; + lejp_destruct(&ctx); + + return 0; +} + + +void +saip_config_destroy(struct sai_power *power) +{ + lwsac_free(&power->ac_conf_head); +} diff --git a/src/power/p-intake.c b/src/power/p-intake.c new file mode 100644 index 0000000..f59a52e --- /dev/null +++ b/src/power/p-intake.c @@ -0,0 +1,170 @@ +/* + * Sai server + * + * Copyright (C) 2019 - 2020 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * This is a ws server run by sai-power, which accepts JSON config from builders + * that want to use it as a helper for managing their power state. + */ + +#include +#include +#include +#include +#include +#include + +#include "p-private.h" + +#include "../common/struct-metadata.c" + + +static int +callback_ws_power(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get( + lws_get_vhost(wsi), lws_get_protocol(wsi)); + uint8_t buf[LWS_PRE + 8192], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - LWS_PRE - 1]; + struct pss *pss = (struct pss *)user; + const char *pvo_resources; + int n; + + (void)end; + (void)p; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + + break; + + /* + * ws connections from builders + */ + + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + return 0; + + case LWS_CALLBACK_ESTABLISHED: + pss->wsi = wsi; + pss->vhd = vhd; + if (!vhd) + return -1; + + if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { + if (lws_hdr_copy(wsi, (char *)start, 64, + WSI_TOKEN_GET_URI) < 0) + return -1; + } +#if defined(LWS_ROLE_H2) + else + if (lws_hdr_copy(wsi, (char *)start, 64, + WSI_TOKEN_HTTP_COLON_PATH) < 0) + return -1; +#endif + + if (!memcmp((char *)start, "/sai", 4)) + start += 4; + + if (!strcmp((char *)start, "/builder")) { + lwsl_info("%s: ESTABLISHED: builder\n", __func__); + pss->wsi = wsi; + /* + * this adds our pss part, but not the logical builder + * yet, until we get the ws rx + */ + lws_dll2_add_head(&pss->same, &vhd->builders); + break; + } + + lwsl_err("%s: unknown URL '%s'\n", __func__, start); + + return -1; + + case LWS_CALLBACK_CLOSED: + lwsac_free(&pss->query_ac); + + lwsl_user("%s: CLOSED builder conn\n", __func__); + /* remove pss from vhd->builders */ + lws_dll2_remove(&pss->same); + + /* + * Destroy any the builder-tracking objects that + * were using this departing connection + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + vhd->server.builder_owner.head) { + sai_plat_t *cb = lws_container_of(p, sai_plat_t, + sai_plat_list); + + if (cb->wsi == wsi) { + /* remove builder object itself from server list */ + cb->wsi = NULL; + lws_dll2_remove(&cb->sai_plat_list); + /* + * free the deserialized builder object, + * everything he pointed to was overallocated + * when his deep copy was made + */ + free(cb); + } + + } lws_end_foreach_dll_safe(p, p1); + + sais_resource_wellknown_remove_pss(&pss->vhd->server, pss); + + if (pss->blob_artifact) { + sqlite3_blob_close(pss->blob_artifact); + pss->blob_artifact = NULL; + } + + if (pss->pdb_artifact) { + sais_event_db_close(pss->vhd, &pss->pdb_artifact); + pss->pdb_artifact = NULL; + } + + /* + * Update the sai-webs about the builder removal, so they + * can update their connected browsers + */ + sais_list_builders(vhd); + break; + + case LWS_CALLBACK_RECEIVE: + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + if (!vhd) { + lwsl_notice("%s: no vhd\n", __func__); + break; + } + + return sais_ws_json_tx_builder(vhd, pss, buf, sizeof(buf)); + + default: +passthru: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +const struct lws_protocols protocol_ws_power = + { "com-warmcat-sai-power", callback_ws_power, sizeof(struct pss), 0 }; diff --git a/src/power/p-private.h b/src/power/p-private.h new file mode 100644 index 0000000..247f1d7 --- /dev/null +++ b/src/power/p-private.h @@ -0,0 +1,124 @@ +/* + * Sai power definitions src/power/b-private.h + * + * Copyright (C) 2019 - 2025 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "../common/include/private.h" + +#include +#if defined(WIN32) +#include +#define read _read +#define open _open +#define close _close +#define write _write +#define mkdir(x,y) _mkdir(x) +#define rmdir _rmdir +#define unlink _unlink +#define HAVE_STRUCT_TIMESPEC +#if defined(pid_t) +#undef pid_t +#endif +#endif +#include +#include + +#define SAI_IDLE_GRACE_US (20 * LWS_US_PER_SEC) + +typedef enum { + PHASE_IDLE, + + PFL_FIRST = 128, + + PHASE_START_ATTACH = PFL_FIRST | 1, + PHASE_SUMM_PLATFORMS = 2, + + PHASE_BUILDING + +} cursor_phase_t; + + + +struct saip_ws_pss; + +typedef struct saip_server_plat { + struct lws_dll2 list; + + const char *name; + const char *power_on_type; + const char *power_on_url; + const char *power_on_mac; + const char *power_off_type; + const char *power_off_url; + +} saip_server_plat_t; + +typedef struct saip_server { + struct lws_dll2 list; + + lws_dll2_owner_t sai_plat_owner; /* list of platforms we offer */ + + struct lws_ss_handle *ss; + + const char *url; + const char *name; +} saip_server_t; + +/* + * This represents this power process as a whole + */ + +struct sai_power { + lws_dll2_owner_t sai_server_owner; /* servers we connect to */ + + struct lwsac *ac_conf_head; + struct lws_context *context; + struct lws_vhost *vhost; + + lws_sorted_usec_list_t sul_idle; + + const char *power_off; + + const char *bind; /* listen socket binding */ + const char *perms; /* user:group */ + + const char *port; /* port we listen on */ +}; + +struct jpargs { + struct sai_power *power; + + saip_server_t *sai_server; + saip_server_plat_t *sai_server_plat; + + sai_plat_server_ref_t *mref; + + int next_server_index; + int next_plat_index; +}; + +extern struct sai_power power; +extern const lws_ss_info_t ssi_saip_server_link_t; +extern const struct lws_protocols protocol_com_warmcat_sai, protocol_ws_power; +int +saip_config_global(struct sai_power *power, const char *d); +extern int saip_config(struct sai_power *power, const char *d); +extern void saip_config_destroy(struct sai_power *power); + + diff --git a/src/power/p-sai.c b/src/power/p-sai.c new file mode 100644 index 0000000..472d665 --- /dev/null +++ b/src/power/p-sai.c @@ -0,0 +1,417 @@ +/* + * sai-power + * + * Copyright (C) 2019 - 2025 Andy Green + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + * + * /----------------------|--- + * b1 --\ <---WOL--\ | \ + * --- [sai-power] --|- sai-server + * b2 --/ plug <---/ | / + * \----------------------|--- + * + * Sai-power is a daemon that runs typically on a machine on the local subnet of + * the bulders that it is used by. When idle, laptop-type builders may suspend + * themselves, but while suspended, they need a helper to watch the sai-server + * for them to see if any tasks appeared for their platform, and to restart the + * builder when that is seen, eg, by sending a WOL magic packet. After that, + * the builder will reconnect to sai-server and deal with the situation that it + * finds at sai-server itself, going back to sleep if nothing to do (eg, because + * another builder for the same platform took the task first). + * + * The same situation exists for the case the builder can't suspend (like many + * SBC) and instead powers off using a smartplug, they also need a helper to + * talk to the smartplug for powerdown after builder shutdown; to watch the + * sai-server on the builder's behalf while it is down; and to power the builder + * back up by switching the builder's smartplug on when tasks for the powered- + * down builder's platform are seen at sai-server. + * + * If there are builders at different sites / subnets (if using WOL) it's no + * problem to have sai-power helpers for each subnet / site pointing to the same + * sai-server. + * + * See p-comms.c for the secure stream template and callbacks for this. + */ + +#include +#include +#include + +#if defined(__linux__) +#include +#endif + +#if defined(__APPLE__) +#include /* for mkdir() */ +#include /* for chown() */ +#endif + +#if defined(WIN32) +#include +#include +#include + +int getpid(void) { return 0; } + +#endif + +#include "p-private.h" + +static const char *config_dir = "/etc/sai/power"; +static int interrupted; +static lws_state_notify_link_t nl; + +struct sai_power power; + +static const char * const default_ss_policy = + "{" + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "99999," + "\"jitterpc\":" "20," + "\"svalidping\":" "100," + "\"svalidhup\":" "110" + "}}" + "]," + + /* + * No certs / trust stores because we will validate using system trust + * store... metadata.url should be set at runtime to something like + * https://warmcat.com/sai + */ + + "\"s\": [" + /* + * The main connection to sai-server + */ + "{\"sai_power\": {" + "\"endpoint\":" "\"${url}\"," + "\"port\":" "443," + "\"protocol\":" "\"ws\"," + "\"ws_subprotocol\":" "\"com-warmcat-sai-power\"," + "\"http_url\":" "\"\"," /* filled in by url */ + "\"nailed_up\":" "true," + "\"tls\":" "true," + "\"retry\":" "\"default\"," + "\"metadata\": [" + "{\"url\": \"\"}" + "]" + "}}," + /* + * The ws server that builders on the local subnet + * connect to for help with power operations + */ + "{\"local\": {" + "\"server\":" "true," + "\"port\":" "3333," + "\"protocol\":" "\"ws\"," + "\"ws_subprotocol\":" "\"com-warmcat-sai-power\"," + "\"tls\":" "false," + "\"metadata\": [" + "{\"path\": \"\"}," + "{\"method\": \"\"}," + "{\"mime\": \"\"}" + "]" + "}}" + "]}" +; + + + +static const struct lws_protocols *pprotocols[] = { +// &protocol_ws_power, + NULL +}; + +LWS_SS_USER_TYPEDEF + char payload[200]; + size_t size; + size_t pos; +} local_srv_t; + +static lws_ss_state_return_t +local_srv_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf, size_t *len, + int *flags) +{ + local_srv_t *g = (local_srv_t *)userobj; + lws_ss_state_return_t r = LWSSSSRET_OK; + + if (g->size == g->pos) + return LWSSSSRET_TX_DONT_SEND; + + if (*len > g->size - g->pos) + *len = g->size - g->pos; + + if (!g->pos) + *flags |= LWSSS_FLAG_SOM; + + memcpy(buf, g->payload + g->pos, *len); + g->pos += *len; + + if (g->pos != g->size) /* more to do */ + r = lws_ss_request_tx(lws_ss_from_user(g)); + else + *flags |= LWSSS_FLAG_EOM; + + lwsl_ss_user(lws_ss_from_user(g), "TX %zu, flags 0x%x, r %d", *len, + (unsigned int)*flags, (int)r); + + return r; +} + +static lws_ss_state_return_t +local_srv_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + local_srv_t *g = (local_srv_t *)userobj; + + lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name(state)); + + switch ((int)state) { + case LWSSSCS_CREATING: + return lws_ss_request_tx(lws_ss_from_user(g)); + + case LWSSSCS_SERVER_TXN: + + lwsl_ss_user(lws_ss_from_user(g), "LWSSSCS_SERVER_TXN"); + + /* + * A transaction is starting on an accepted connection. Say + * that we're OK with the transaction, prepare the user + * object with the response, and request tx to start sending it. + */ + lws_ss_server_ack(lws_ss_from_user(g), 0); + + if (lws_ss_set_metadata(lws_ss_from_user(g), "mime", "text/html", 9)) + return LWSSSSRET_DISCONNECT_ME; + + g->size = (size_t)lws_snprintf(g->payload, sizeof(g->payload), + "Hello World: %lu", + (unsigned long)lws_now_usecs()); + g->pos = 0; + + return lws_ss_request_tx_len(lws_ss_from_user(g), + (unsigned long)g->size); + } + + return LWSSSSRET_OK; +} + + +LWS_SS_INFO("local", local_srv_t) + .tx = local_srv_tx, + .state = local_srv_state, +}; + + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *cx = lws_system_context_from_system_mgr(mgr); + + /* + * For the things we care about, let's notice if we are trying to get + * past them when we haven't solved them yet, and make the system + * state wait while we trigger the dependent action. + */ + switch (target) { + + case LWS_SYSTATE_OPERATIONAL: + if (current != LWS_SYSTATE_OPERATIONAL) + break; + + lwsl_cx_user(cx, "LWS_SYSTATE_OPERATIONAL"); + + /* create our LAN-facing sai-power server / listener */ + + if (lws_ss_create(cx, 0, &ssi_local_srv_t, NULL, NULL, NULL, NULL)) + return 1; + + /* + * For each server... a single connection + */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1, + power.sai_server_owner.head) { + saip_server_t *sps = lws_container_of(mp, + struct saip_server, list); + + lwsl_user("%s: OPERATIONAL: server url %p %s\n", __func__, sps, sps->url); + + if (lws_ss_create(cx, 0, &ssi_saip_server_link_t, sps, + &sps->ss, NULL, NULL)) { + lwsl_err("%s: failed to create secure stream\n", + __func__); + return -1; + } + + } lws_end_foreach_dll_safe(mp, mp1); + + + break; + } + + return 0; +} + +/* + * The grace time is up, ask for the suspend + */ + +void +sul_idle_cb(lws_sorted_usec_list_t *sul) +{ + +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +void sigint_handler(int sig) +{ + interrupted = 1; +} + + +int main(int argc, const char **argv) +{ + int logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE; + struct lws_context_creation_info info; +#if defined(WIN32) + char temp[256], stg_config_dir[256]; +#endif + const char *p; + + lws_context_info_defaults(&info, NULL); + + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); + + if ((p = lws_cmdline_option(argc, argv, "-c"))) + config_dir = p; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + if (lws_cmdline_option(argc, argv, "-D")) { + if (lws_daemonize("/var/run/sai_power.pid")) + return 1; + lws_set_log_level(logs, lwsl_emit_syslog); + } else +#endif + + lws_set_log_level(logs, NULL); + +#if defined(WIN32) + { + PWSTR wdi = NULL; + + if (SHGetKnownFolderPath(&FOLDERID_ProgramData, + 0, NULL, &wdi) != S_OK) { + lwsl_err("%s: unable to get config dir\n", __func__); + return 1; + } + + if (WideCharToMultiByte(CP_ACP, 0, wdi, -1, temp, + sizeof(temp), 0, NULL) <= 0) { + lwsl_err("%s: problem with string encoding\n", __func__); + return 1; + } + + lws_snprintf(stg_config_dir, sizeof(stg_config_dir), + "%s\\sai\\power\\", temp); + + config_dir = stg_config_dir; + CoTaskMemFree(wdi); + } +#endif + + /* + * Let's parse the global bits out of the config + */ + + lwsl_user("Sai Power - " + "Copyright (C) 2019-2025 Andy Green \n"); + lwsl_user(" sai-power [-c ]\n"); + + info.pprotocols = pprotocols; + info.uid = 883; + info.pt_serv_buf_size = 32 * 1024; + info.rlimit_nofile = 20000; + + signal(SIGINT, sigint_handler); + + info.pss_policies_json = default_ss_policy; + info.fd_limit_per_thread = 1 + 256 + 1; + + /* hook up our lws_system state notifier */ + + nl.name = "sai-power"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + /* create the lws context */ + + power.context = lws_create_context(&info); + if (!power.context) { + lwsl_err("lws init failed\n"); + return 1; + } + + + lwsl_notice("%s: config dir %s\n", __func__, config_dir); + if (saip_config_global(&power, config_dir)) { + lwsl_err("%s: global config failed\n", __func__); + + return 1; + } + + power.vhost = lws_create_vhost(power.context, &info); + if (!power.vhost) { + lwsl_err("Failed to create tls vhost\n"); + goto bail; + } + + while (!lws_service(power.context, 0) && !interrupted) + ; + +bail: + + /* destroy the connections to the servers */ + + lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1, + power.sai_server_owner.head) { + struct saip_server *sps = lws_container_of(p, + struct saip_server, list); + + lws_dll2_remove(&sps->list); + lws_ss_destroy(&sps->ss); + + } lws_end_foreach_dll_safe(p, p1); + + saip_config_destroy(&power); + + lws_context_destroy(power.context); + + return 0; +} diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 74e4e8d..2e29af7 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -15,10 +15,10 @@ set(SRCS ) set(requirements 1) -require_lws_config(LWS_WITH_STRUCT_SQLITE3 1 requirements) require_lws_config(LWS_WITH_SERVER 1 requirements) require_lws_config(LWS_WITH_GENCRYPTO 1 requirements) require_lws_config(LWS_WITH_UNIX_SOCK 1 requirements) +require_lws_config(LWS_WITH_STRUCT_SQLITE3 1 requirements) if (requirements) add_executable(${SUB} ${SRCS}) diff --git a/src/server/s-comms.c b/src/server/s-comms.c index 2233271..22ef381 100644 --- a/src/server/s-comms.c +++ b/src/server/s-comms.c @@ -115,7 +115,7 @@ sais_event_db_ensure_open(struct vhd *vhd, const char *event_uuid, char filepath[256], saf[33]; sais_sqlite_cache_t *sc; - lwsl_notice("%s: (sai-server) entry\n", __func__); + // lwsl_notice("%s: (sai-server) entry\n", __func__); if (*ppdb) return 0; @@ -792,3 +792,138 @@ passthru: const struct lws_protocols protocol_ws = { "com-warmcat-sai", callback_ws, sizeof(struct pss), 0 }; + + + /* + * This is where the sai-power connections to us end up. They + * want to know summaries of platforms that have open jobs on any + * event, re-sent every time it might have changed. + */ + + +static int +callback_ws_power(struct lws *wsi, enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + struct vhd *vhd = (struct vhd *)lws_protocol_vh_priv_get( + lws_get_vhost(wsi), lws_get_protocol(wsi)); + uint8_t buf[LWS_PRE + 8192], *start = &buf[LWS_PRE], *p = start, + *end = &buf[sizeof(buf) - LWS_PRE - 1]; + struct pss_power *pss = (struct pss_power *)user; + int n; + size_t m; + + (void)end; + (void)p; + + switch (reason) { + case LWS_CALLBACK_PROTOCOL_INIT: + vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), + lws_get_protocol(wsi), + sizeof(struct vhd)); + + vhd->context = lws_get_context(wsi); + vhd->vhost = lws_get_vhost(wsi); + + if (lws_pvo_get_str(in, "database", &vhd->sqlite3_path_lhs)) { + lwsl_err("%s: database pvo required\n", __func__); + return -1; + } + + lws_snprintf((char *)buf, sizeof(buf), "%s-events.sqlite3", + vhd->sqlite3_path_lhs); + + if (lws_struct_sq3_open(vhd->context, (char *)buf, 1, + &vhd->server.pdb)) { + lwsl_err("%s: Unable to open session db %s: %s\n", + __func__, vhd->sqlite3_path_lhs, sqlite3_errmsg( + vhd->server.pdb)); + + return -1; + } + break; + + case LWS_CALLBACK_PROTOCOL_DESTROY: + goto passthru; + + /* + * ws connections from sai-power clients + */ + + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + return 0; + + case LWS_CALLBACK_ESTABLISHED: + pss->wsi = wsi; + pss->vhd = vhd; + if (!vhd) + return -1; + + if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { + if (lws_hdr_copy(wsi, (char *)start, 64, + WSI_TOKEN_GET_URI) < 0) + return -1; + } +#if defined(LWS_ROLE_H2) + else + if (lws_hdr_copy(wsi, (char *)start, 64, + WSI_TOKEN_HTTP_COLON_PATH) < 0) + return -1; +#endif + + lws_dll2_add_head(&pss->same, &vhd->sai_powers); + + sais_platforms_with_tasks_pending(vhd); + + break; + + case LWS_CALLBACK_CLOSED: + + lwsl_user("%s: CLOSED sai-power conn\n", __func__); + /* remove pss from vhd->sai_powers */ + lws_dll2_remove(&pss->same); + break; + + case LWS_CALLBACK_RECEIVE: + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + if (!vhd) { + lwsl_notice("%s: no vhd\n", __func__); + break; + } + + n = 0; + lws_start_foreach_dll(struct lws_dll2 *, px, vhd->pending_plats.head) { + sais_plat_t *pl = lws_container_of(px, sais_plat_t, list); + + if (n) + *p++ = ','; + m = strlen(pl->plat); + if (lws_ptr_diff_size_t(end, p) < m + 2) + break; + memcpy(p, pl->plat, m); + p += m; + n = 1; + + } lws_end_foreach_dll(px); + + + lwsl_hexdump_notice(start, lws_ptr_diff_size_t(p, start)); + + if (lws_write(pss->wsi, start, lws_ptr_diff_size_t(p, start), + LWS_WRITE_TEXT) < 0) + return -1; + + break; + + default: +passthru: + break; + } + + return lws_callback_http_dummy(wsi, reason, user, in, len); +} + +const struct lws_protocols protocol_ws_power = + { "com-warmcat-sai-power", callback_ws_power, sizeof(struct pss_power), 0 }; diff --git a/src/server/s-notification.c b/src/server/s-notification.c index 019d627..6028beb 100644 --- a/src/server/s-notification.c +++ b/src/server/s-notification.c @@ -557,6 +557,12 @@ sai_saifile_lejp_cb(struct lejp_ctx *ctx, char reason) sais_event_db_close(pss->vhd, &pdb); + /* + * Recompute startable task platforms and broadcast to all sai-power, + * after there has been a change in tasks + */ + sais_platforms_with_tasks_pending(pss->vhd); + // lwsl_notice("%s: New test '%s', '%s', '%s'\n", __func__, // sn->t.taskname, sn->t.cmake, sn->t.packages); diff --git a/src/server/s-private.h b/src/server/s-private.h index 68101ec..f6bcd67 100644 --- a/src/server/s-private.h +++ b/src/server/s-private.h @@ -151,6 +151,13 @@ struct pss { uint8_t ovstate; /* SOS_ substate when doing overview */ }; +struct pss_power { + struct vhd *vhd; + struct lws *wsi; + + struct lws_dll2 same; /* owner: vhd.builders */ +}; + typedef struct sais_sqlite_cache { lws_dll2_t list; char uuid[65]; @@ -159,6 +166,12 @@ typedef struct sais_sqlite_cache { int refcount; } sais_sqlite_cache_t; + +typedef struct sais_plat { + lws_dll2_t list; + const char *plat; +} sais_plat_t; + struct vhd { struct lws_context *context; struct lws_vhost *vhost; @@ -168,7 +181,11 @@ struct vhd { char json_builders[8192]; /* pss lists */ - struct lws_dll2_owner builders; + struct lws_dll2_owner builders; + struct lws_dll2_owner sai_powers; + struct lws_dll2_owner pending_plats; + + struct lwsac *ac_plats; const char *sqlite3_path_lhs; @@ -189,7 +206,7 @@ sai_lws_context_from_json(const char *config_dir, struct lws_context_creation_info *info, const struct lws_protocols **pprotocols, const char *jpol); -extern const struct lws_protocols protocol_ws; +extern const struct lws_protocols protocol_ws, protocol_ws_power; int sai_notification_file_upload_cb(void *data, const char *name, @@ -288,3 +305,6 @@ sais_resource_destroy_queued_by_cookie(sais_t *sais, const char *cookie); void sais_resource_rr_destroy(sai_resource_requisition_t *rr); + +int +sais_platforms_with_tasks_pending(struct vhd *vhd); diff --git a/src/server/s-sai.c b/src/server/s-sai.c index c119d5e..b77c52c 100644 --- a/src/server/s-sai.c +++ b/src/server/s-sai.c @@ -55,6 +55,7 @@ static const char * const default_ss_policy = static const struct lws_protocols *pprotocols[] = { &protocol_ws, + &protocol_ws_power, #if defined(LWS_WITH_SYS_METRICS) && defined(LWS_WITH_PLUGINS_BUILTIN) &lws_openmetrics_export_protocols[LWSOMPROIDX_PROX_HTTP_SERVER], &lws_openmetrics_export_protocols[LWSOMPROIDX_PROX_WS_SERVER], @@ -81,7 +82,7 @@ int main(int argc, const char **argv) logs = atoi(p); lws_set_log_level(logs, NULL); - lwsl_user("Sai Server - Copyright (C) 2019-2020 Andy Green \n"); + lwsl_user("Sai Server - Copyright (C) 2019-2025 Andy Green \n"); if ((p = lws_cmdline_option(argc, argv, "-c"))) conf = p; diff --git a/src/server/s-task.c b/src/server/s-task.c index 0261811..cf20cbb 100644 --- a/src/server/s-task.c +++ b/src/server/s-task.c @@ -329,6 +329,154 @@ bail: return NULL; } +/* + * If the plat name is already listed, just return with 1. + * Otherwise add to the ac and linked-list for unique startable plat names and + * return 0. + */ + +static int +sais_find_or_add_pending_plat(struct vhd *vhd, const char *name) +{ + sais_plat_t *sp; + + lws_start_foreach_dll(struct lws_dll2 *, p, vhd->pending_plats.head) { + sais_plat_t *pl = lws_container_of(p, sais_plat_t, list); + + if (!strcmp(&pl->plat[1], name)) + return 1; + + } lws_end_foreach_dll(p); + + /* platform name is new, make an entry in the ac */ + + sp = lwsac_use_zero(&vhd->ac_plats, sizeof(sais_plat_t) + strlen(name) + 1, 512); + + sp->plat = (const char *)&sp[1]; /* start of overcommit */ + memcpy(&sp[1], name, strlen(name) + 1); + + lws_dll2_add_tail(&sp->list, &vhd->pending_plats); + + return 0; +} + +static void +sais_destroy_pending_plat_list(struct vhd *vhd) +{ + /* + * We can just drop everything in the owner and drop the ac to destroy + */ + lws_dll2_owner_clear(&vhd->pending_plats); + lwsac_free(&vhd->ac_plats); +} + +static void +sais_notify_all_sai_power(struct vhd *vhd) +{ + lws_start_foreach_dll(struct lws_dll2 *, p, vhd->sai_powers.head) { + struct pss_power *pss = lws_container_of(p, struct pss_power, same); + + lws_callback_on_writable(pss->wsi); + + } lws_end_foreach_dll(p); +} + +/* + * Find out which platforms on this server have pending tasks + */ + +int +sais_platforms_with_tasks_pending(struct vhd *vhd) +{ + struct lwsac *ac = NULL; + char pf[128]; + lws_dll2_owner_t o; + int n; + + lwsl_err("%s: ++++++++ entry\n", __func__); + + /* lose everything we were holding on to from last time */ + sais_destroy_pending_plat_list(vhd); + + /* + * Collect a list of events that still have any open tasks + */ + + lws_snprintf(pf, sizeof(pf)," and (state != 3 and state != 4 and state != 5) and created < %llu", + (unsigned long long)(lws_now_secs() - 10)); + + n = lws_struct_sq3_deserialize(vhd->server.pdb, pf, "created desc ", + lsm_schema_sq3_map_event, &o, &ac, 0, 10); + + if (n < 0 || !o.head) { + /* error, or there are no events that aren't complete */ + goto bail; + } + + + lwsl_err("%s: starting scan\n", __func__); + + /* + * Iterate through the events looking at his event-specific database + * for platforms that have pending tasks... + */ + + lws_start_foreach_dll(struct lws_dll2 *, p, o.head) { + sai_event_t *e = lws_container_of(p, sai_event_t, list); + sqlite3 *pdb = NULL; + sqlite3_stmt *sm; + int n; + + if (!sais_event_db_ensure_open(vhd, e->uuid, 0, &pdb)) { + + if (sqlite3_prepare_v2(pdb, "select distinct platform " + "from tasks where " + "(state != 3 and state != 4 and state != 5)", -1, &sm, + NULL) != SQLITE_OK) { + lwsl_err("%s: Unable to %s\n", + __func__, sqlite3_errmsg(pdb)); + + goto bail; + } + + do { + n = sqlite3_step(sm); + if (n == SQLITE_ROW) { + lwsl_err("%s: scanned plat %s\n", __func__, (const char *)sqlite3_column_text(sm, 0)); + sais_find_or_add_pending_plat(vhd, + (const char *)sqlite3_column_text(sm, 0)); + } + } while (n == SQLITE_ROW); + + sqlite3_reset(sm); + sqlite3_finalize(sm); + + if (n != SQLITE_DONE) { + n = sqlite3_extended_errcode(pdb); + if (!n) + lwsl_info("%s: failed\n", __func__); + + lwsl_err("%s: %d: Unable to perform: %s\n", + __func__, n, sqlite3_errmsg(pdb)); + } + + sais_event_db_close(vhd, &pdb); + } + + } lws_end_foreach_dll(p); + + sais_notify_all_sai_power(vhd); + + lwsac_free(&ac); + + return 0; + +bail: + lwsac_free(&ac); + + return 1; +} + int sais_task_cancel(struct vhd *vhd, const char *task_uuid) { @@ -359,6 +507,12 @@ sais_task_cancel(struct vhd *vhd, const char *task_uuid) sais_taskchange(vhd->h_ss_websrv, task_uuid, SAIES_CANCELLED); + /* + * Recompute startable task platforms and broadcast to all sai-power, + * after there has been a change in tasks + */ + sais_platforms_with_tasks_pending(vhd); + return 0; } @@ -413,6 +567,12 @@ sais_task_reset(struct vhd *vhd, const char *task_uuid) lws_sul_schedule(vhd->context, 0, &vhd->sul_central, sais_central_cb, 1); + /* + * Recompute startable task platforms and broadcast to all sai-power, + * after there has been a change in tasks + */ + sais_platforms_with_tasks_pending(vhd); + return 0; } diff --git a/src/server/s-websrv.c b/src/server/s-websrv.c index 1db4f46..f573b6e 100644 --- a/src/server/s-websrv.c +++ b/src/server/s-websrv.c @@ -509,8 +509,8 @@ websrvss_srv_state(void *userobj, void *sh, lws_ss_constate_t state, { websrvss_srv_t *m = (websrvss_srv_t *)userobj; - lwsl_user("%s: %p %s, ord 0x%x\n", __func__, m->ss, - lws_ss_state_name((int)state), (unsigned int)ack); + // lwsl_user("%s: %p %s, ord 0x%x\n", __func__, m->ss, + // lws_ss_state_name((int)state), (unsigned int)ack); switch (state) { case LWSSSCS_DISCONNECTED: diff --git a/src/server/s-ws-builder.c b/src/server/s-ws-builder.c index 638bc72..febc3bd 100644 --- a/src/server/s-ws-builder.c +++ b/src/server/s-ws-builder.c @@ -229,7 +229,7 @@ sai_sql3_get_uint64_cb(void *user, int cols, char **values, char **name) } /* - * Master received a communication from a builder + * Server received a communication from a builder */ int