| /*
* sai-builder
*
* Copyright (C) 2019 - 2020 Andy Green <andy@warmcat.com>
*
* 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
*
* Sai-builder uses a secure streams template to make the client connections to
* the servers listed in /etc/sai/builder/conf JSON config. The URL in the
* config is substituted for the endpoint URL at runtime.
*
* See b-comms.c for the secure stream template and callbacks for this.
*/
#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include <limits.h>
#include <stdlib.h>
#include <sys/types.h>
#if !defined(WIN32)
#include <pwd.h>
#include <grp.h>
#endif
#if defined(__linux__) || defined(__APPLE__)
#include <unistd.h>
#endif
#if defined(__APPLE__)
#include <sys/stat.h> /* for mkdir() */
#endif
#if defined(WIN32)
#include <initguid.h>
#include <KnownFolders.h>
#include <Shlobj.h>
#include <processthreadsapi.h>
#include <handleapi.h>
#if !defined(PATH_MAX)
#define PATH_MAX MAX_PATH
#endif
int getpid(void) { return 0; }
#endif
#include "b-private.h"
static int
sai_deletion_worker(const char *home_dir)
{
char path[PATH_MAX], *p;
ssize_t n;
lwsl_notice("%s: deletion worker started\n", __func__);
#if defined(WIN32)
/*
* On Windows, stdin is not a pipe from the parent but a handle
* value passed on the commandline
*/
// detach from console...
FreeConsole();
#endif
while (1) {
n = read(0, path, sizeof(path) - 1);
if (n <= 0) {
lwsl_notice("%s: pipe closed, exiting\n", __func__);
return 0;
}
path[n] = '\0';
p = path;
/* sanitize: no .. or / or \ */
while (*p) {
if (*p == '.' || *p == '/' || *p == '\\') {
lwsl_err("%s: invalid chars in delete path '%s'\n",
__func__, path);
p = NULL;
break;
}
p++;
}
if (!p)
continue;
lws_snprintf(path, sizeof(path), "%s/jobs/%s", home_dir, path);
if (lws_dir(path, NULL, lws_dir_rm_rf_cb))
lwsl_err("%s: failed to delete %s\n", __func__, path);
}
return 0;
}
/*
* Periodically (eg, once per hour) we walk the jobs dir and find subdirs
* that are older than a day.
*
* These represent failed jobs that were left for inspection, but should now
* be cleaned up.
*
* We are careful not to delete anything that is part of an ongoing job.
*/
struct active_job_uuids {
lws_dll2_owner_t owner;
};
struct active_job_uuid {
lws_dll2_t list;
char uuid[65];
};
static int
scan_jobs_dir_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
{
struct active_job_uuids *active = (struct active_job_uuids *)user;
struct active_job_uuid *aj;
char path[512];
struct stat sb;
if (lde->type != LDOT_DIR || lde->name[0] == '.')
return 0;
lws_start_foreach_dll(struct lws_dll2 *, p, active->owner.head) {
aj = lws_container_of(p, struct active_job_uuid, list);
if (!strcmp(aj->uuid, lde->name))
/* it's an active job, leave it alone */
return 0;
} lws_end_foreach_dll(p);
lws_snprintf(path, sizeof(path), "%s/%s", dirpath, lde->name);
if (stat(path, &sb))
return 0;
/* older than 24h? */
if (((uint64_t)lws_now_secs() - (uint64_t)sb.st_mtime) > 24ull * 3600u) {
lwsl_notice("%s: requesting removal of old job dir %s\n",
__func__, path);
#if !defined(WIN32)
if (write(builder.pipe_master_wr, lde->name,
LWS_POSIX_LENGTH_CAST(strlen(lde->name))) != (ssize_t)strlen(lde->name))
lwsl_err("%s: failed to write to deletion worker\n",
__func__);
#else
{
DWORD written;
if (!WriteFile(builder.pipe_master_wr_win, lde->name,
(DWORD)strlen(lde->name), &written, NULL))
lwsl_err("%s: failed to write to deletion worker\n",
__func__);
}
#endif
}
return 0;
}
static void
sul_cleanup_jobs_cb(lws_sorted_usec_list_t *sul)
{
struct sai_builder *b = lws_container_of(sul, struct sai_builder,
sul_cleanup_jobs);
struct active_job_uuids active;
struct lwsac *ac = NULL;
char path[256];
memset(&active, 0, sizeof(active));
/*
* We must not delete any active job directories, find out the uuids
* of any active jobs
*/
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
b->sai_plat_owner.head) {
struct sai_plat *sp = lws_container_of(d,
struct sai_plat, sai_plat_list);
lws_start_foreach_dll_safe(struct lws_dll2 *, d2, d3,
sp->nspawn_owner.head) {
struct sai_nspawn *ns = lws_container_of(d2,
struct sai_nspawn, list);
struct active_job_uuid *aj;
if (!ns->task)
continue;
aj = lwsac_use_zero(&ac, sizeof(*aj), 64);
if (!aj)
continue;
lws_strncpy(aj->uuid, ns->task->uuid, sizeof(aj->uuid));
lws_dll2_add_tail(&aj->list, &active.owner);
} lws_end_foreach_dll_safe(d2, d3);
} lws_end_foreach_dll_safe(d, d1);
/*
* Now we have the active job uuids, scan the jobs dir and check
* for old, inactive job dirs to reap
*/
lws_snprintf(path, sizeof(path), "%s/jobs", b->home);
lws_dir(path, &active, scan_jobs_dir_cb);
lwsac_free(&ac);
lws_sul_schedule(b->context, 0, &b->sul_cleanup_jobs,
sul_cleanup_jobs_cb, 60 * LWS_US_PER_SEC);
}
static const char *config_dir = "/etc/sai/builder";
static int interrupted;
static lws_state_notify_link_t nl;
struct lws_spawn_piped *lsp_suspender;
struct sai_builder builder;
extern struct lws_protocols protocol_stdxxx;
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_builder\": {"
"\"endpoint\":" "\"${url}\","
"\"port\":" "443,"
"\"protocol\":" "\"ws\","
"\"ws_subprotocol\":" "\"com-warmcat-sai\","
"\"http_url\":" "\"\"," /* filled in by url */
"\"nailed_up\":" "true,"
"\"tls\":" "true,"
"\"retry\":" "\"default\","
"\"metadata\": ["
"{\"url\": \"\"}"
"]"
"}},"
/*
* Ephemeral connections to the same server carrying artifact
* JSON + bulk data
*/
"{\"sai_artifact\": {"
"\"endpoint\":" "\"${url}\","
"\"port\":" "443,"
"\"protocol\":" "\"ws\","
"\"ws_subprotocol\":" "\"com-warmcat-sai\","
"\"http_url\":" "\"\"," /* filled in by url */
"\"tls\":" "true,"
"\"opportunistic\":" "true,"
"\"ws_binary\":" "true," /* we're sending binary */
"\"retry\":" "\"default\","
"\"metadata\": ["
"{\"url\": \"\"}"
"]"
"}},"
/*
* Used to connect to sai-power to ask for power-off
*/
"{\"sai_power\": {"
"\"endpoint\":" "\"${url}\","
"\"protocol\":" "\"h1\","
"\"http_url\":" "\"\"," /* filled in by url */
"\"http_method\":" "\"GET\","
"\"retry\":" "\"default\","
"\"metadata\": ["
"{\"url\": \"\"}"
"]"
"}}"
"]}"
;
static const struct lws_protocols *pprotocols[] = {
&protocol_stdxxx,
&protocol_logproxy,
&protocol_resproxy,
#if defined(LWS_WITH_SYS_METRICS) && defined(LWS_WITH_PLUGINS_BUILTIN)
&lws_openmetrics_export_protocols[LWSOMPROIDX_PROX_WS_CLIENT],
#else
NULL,
#endif
NULL
};
static int lpidx;
static char vhnames[256], *pv = vhnames;
static struct lws_protocol_vhost_options
pvo1c = {
NULL, /* "next" pvo linked-list */
NULL, /* "child" pvo linked-list */
"ba-secret", /* protocol name we belong to on this vhost */
"ok" /* set at runtime from conf */
},
pvo1b = {
&pvo1c, /* "next" pvo linked-list */
NULL, /* "child" pvo linked-list */
"metrics-proxy-path", /* protocol name we belong to on this vhost */
"ok" /* set at runtime from conf */
},
pvo1a = {
&pvo1b, /* "next" pvo linked-list */
NULL, /* "child" pvo linked-list */
"ws-server-uri", /* protocol name we belong to on this vhost */
"ok" /* set at runtime from conf */
},
pvo1 = { /* starting point for metrics proxy */
NULL, /* "next" pvo linked-list */
&pvo1a, /* "child" pvo linked-list */
"lws-openmetrics-prox-client", /* protocol name we belong to on this vhost */
"ok" /* ignored */
},
pvo = { /* starting point for logproxy */
NULL, /* "next" pvo linked-list */
NULL, /* "child" pvo linked-list */
"protocol-logproxy", /* protocol name we belong to on this vhost */
"ok" /* ignored */
},
pvo_resproxy = { /* starting point for resproxy */
NULL, /* "next" pvo linked-list */
NULL, /* "child" pvo linked-list */
"protocol-resproxy", /* protocol name we belong to on this vhost */
"ok" /* ignored */
};
void *
saib_thread_suspend(void *d)
{
return NULL;
}
int
saib_create_listen_uds(struct lws_context *context, struct saib_logproxy *lp,
struct lws_vhost **vhost)
{
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.vhost_name = pv;
pv += lws_snprintf(pv, sizeof(vhnames) - (size_t)(pv - vhnames), "logproxy.%d", lpidx++) + 1;
info.options = LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG |
LWS_SERVER_OPTION_UNIX_SOCK;
info.iface = lp->sockpath;
info.listen_accept_role = "raw-skt";
info.listen_accept_protocol = "protocol-logproxy";
info.user = lp;
info.pvo = &pvo;
info.pprotocols = pprotocols;
#if !defined(__linux__)
unlink(lp->sockpath);
#endif
lwsl_notice("%s: %s.%s\n", __func__, info.vhost_name, lp->sockpath);
*vhost = lws_create_vhost(context, &info);
if (!*vhost) {
lwsl_notice("%s: failed to create vh %s\n", __func__,
info.vhost_name);
return -1;
}
return 0;
}
/*
* We create one of these per server we connected to
*/
int
saib_create_resproxy_listen_uds(struct lws_context *context,
struct sai_plat_server *spm)
{
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.vhost_name = pv;
pv += lws_snprintf(pv, sizeof(vhnames) - (size_t)(pv - vhnames),
"resproxy.%u.%d", (unsigned int)getpid(), spm->index) + 1;
info.options = LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG |
LWS_SERVER_OPTION_UNIX_SOCK;
info.iface = spm->resproxy_path;
info.listen_accept_role = "raw-skt";
info.listen_accept_protocol = "protocol-resproxy";
info.user = spm;
info.pvo = &pvo_resproxy;
info.pprotocols = pprotocols;
lwsl_notice("%s: Created resproxy %s.%s\n", __func__, info.vhost_name,
spm->resproxy_path);
if (!lws_create_vhost(context, &info)) {
lwsl_notice("%s: failed to create vh %s\n", __func__,
info.vhost_name);
return -1;
}
return 0;
}
/*
* This is used to check with sai-power if we should stay up (due to the power
* being turned on manually)
*/
LWS_SS_USER_TYPEDEF
char payload[200];
size_t size;
size_t pos;
} saib_power_stay_t;
static lws_ss_state_return_t
saib_power_stay_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
if (len < 1)
return 0;
builder.stay = *buf == '1';
// lwsl_err("%s: stay %d\n", __func__, builder.stay);
if (builder.stay) {
/*
* We need to deal with finding we have been manually powered-on.
* Just cancel any pending grace period
*/
lws_sul_cancel(&builder.sul_idle);
// lwsl_warn("%s: %s: stay applied: cancelled idle grace time\n",
// __func__, builder.host);
} else {
/*
* If any plat on this builder has tasks, just
* leave it
*/
lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1,
builder.sai_plat_owner.head) {
struct sai_plat *sp = lws_container_of(mp, struct sai_plat,
sai_plat_list);
if (sp->nspawn_owner.count) {
lwsl_warn("%s: cancelling idle grace time as ongoing task steps\n", __func__);
lws_sul_cancel(&builder.sul_idle);
return 0;
}
} lws_end_foreach_dll_safe(mp, mp1);
/*
* if no ongoing tasks, and we want to go OFF, then start
* the idle grace timer. This will get cancelled if
* we start a task during the grace time, otherwise it will
* expire and do the power-off or suspend
*/
if (lws_dll2_is_detached(&builder.sul_idle.list)) {
lwsl_warn("%s: %s: stay dropped: starting idle grace time\n",
__func__, builder.host);
lws_sul_schedule(builder.context, 0, &builder.sul_idle,
sul_idle_cb, SAI_IDLE_GRACE_US);
}
}
return 0;
}
static lws_ss_state_return_t
sai_power_stay_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
saib_power_stay_t *g = (saib_power_stay_t *)userobj;
lws_ss_state_return_t r;
// lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name(state));
switch ((int)state) {
case LWSSSCS_CREATING:
if (!builder.url_sai_power)
return LWSSSSRET_DESTROY_ME;
snprintf(builder.path, sizeof(builder.path) - 1, "%s/stay/%s",
builder.url_sai_power, builder.host);
r = lws_ss_set_metadata(lws_ss_from_user(g), "url", builder.path, strlen(builder.path));
if (r)
lwsl_err("%s: set_metadata said %d\n", __func__, (int)r);
return lws_ss_request_tx(lws_ss_from_user(g));
}
return LWSSSSRET_OK;
}
LWS_SS_INFO("sai_power", saib_power_stay_t)
.rx = saib_power_stay_rx,
.state = sai_power_stay_state,
};
/*
* We need to keep polling to see if he manually told our sai-power that we
* should either stay up (manual power-on) or be prepared to go down (manual
* power-off)
*/
void
sul_stay_cb(lws_sorted_usec_list_t *sul)
{
lws_ss_state_return_t r;
/* nothing to do if no sai-power coordinates given */
if (!builder.url_sai_power)
return;
r = lws_ss_client_connect(builder.ss_stay);
if (r)
lwsl_ss_err(builder.ss_stay, "Unable to start stay connection (%d)", (int)r);
lws_sul_schedule(builder.context, 0, &builder.sul_stay,
sul_stay_cb, SAI_STAY_POLL_US);
}
static int
app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link,
int current, int target)
{
/*
* 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;
/*
* The builder JSON conf listed servers we want to connect to,
* let's collect the config, make a ss for each and add the
* saim into an lws_dll2 list owned by
* builder->builder->sai_plat_owner
*/
lwsl_notice("%s: starting platform config\n", __func__);
if (saib_config(&builder, config_dir)) {
lwsl_err("%s: config failed\n", __func__);
return 1;
}
/*
* For each platform...
*/
/*
* Create the resource proxy listeners, one per server link
*/
lwsl_notice("%s: creating resource proxy listeners\n", __func__);
lws_start_foreach_dll(struct lws_dll2 *, pxx,
builder.sai_plat_server_owner.head) {
struct sai_plat_server *spm = lws_container_of(pxx, sai_plat_server_t, list);
lws_snprintf(spm->resproxy_path, sizeof(spm->resproxy_path),
#if defined(__linux__)
UDS_PATHNAME_RESPROXY".%u.%d", getpid(),
#else
UDS_PATHNAME_RESPROXY"/%d",
#endif
spm->index);
lwsl_notice("%s: creating %s\n", __func__, spm->resproxy_path);
saib_create_resproxy_listen_uds(builder.context, spm);
} lws_end_foreach_dll(pxx);
/*
* ss used to query sai-power about stay situation
*/
if (lws_ss_create(builder.context, 0, &ssi_saib_power_stay_t,
NULL, &builder.ss_stay, NULL, NULL)) {
lwsl_err("%s: failed to create sai-power-stay ss\n", __func__);
return 1;
}
lws_sul_schedule(builder.context, 0, &builder.sul_stay,
sul_stay_cb, 1000);
lws_sul_schedule(builder.context, 0, &builder.sul_cleanup_jobs,
sul_cleanup_jobs_cb, 3600 * LWS_US_PER_SEC);
break;
}
return 0;
}
/*
* This is used to fire http request to sai-power for power-down
*/
LWS_SS_USER_TYPEDEF
char payload[200];
size_t size;
size_t pos;
} saib_power_link_t;
static lws_ss_state_return_t
saib_power_link_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
saib_power_link_t *g = (saib_power_link_t *)userobj;
lws_ss_state_return_t r;
char path[256];
lwsl_ss_user(lws_ss_from_user(g), "state %s", lws_ss_state_name(state));
switch (state) {
case LWSSSCS_CREATING:
snprintf(path, sizeof(path) - 1, "%s/auto-power-off/%s",
builder.url_sai_power,
(const char *)lws_ss_opaque_from_user(g));
lwsl_notice("%s: setting url metadata %s\n", __func__, path);
r = lws_ss_set_metadata(lws_ss_from_user(g), "url", path, strlen(path));
if (r)
lwsl_err("%s: set_metadata said %d\n", __func__, (int)r);
lws_ss_start_timeout(lws_ss_from_user(g), 3000); /* 3 sec */
return lws_ss_request_tx(lws_ss_from_user(g));
case LWSSSCS_TIMEOUT:
break;
default:
break;
}
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
saib_power_link_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
uint8_t te = 0;
ssize_t n;
if (len < 4 || !(flags & LWSSS_FLAG_SOM))
return 0;
if (memcmp(buf, "ACK:", 4)) {
lwsl_warn("%s: sai-power didn't start power-off: %.*s\n",
__func__, (int)len, (const char *)buf);
return LWSSSSRET_OK;
}
lwsl_notice("%s: sai-power scheduling power-off: doing shutdown...\n", __func__);
/*
* In the grace time for actioning the power-off, we should shutdown
* cleanly
*/
n = write(lws_spawn_get_fd_stdxxx(lsp_suspender, 0), &te, 1);
if (n != 1)
lwsl_err("%s: unable to request shutdown\n", __func__);
return LWSSSSRET_OK;
}
LWS_SS_INFO("sai_power", saib_power_link_t)
.rx = saib_power_link_rx,
.state = saib_power_link_state,
};
/*
* The grace time is up, ask for the suspend
*/
void
sul_idle_cb(lws_sorted_usec_list_t *sul)
{
#if !defined(WIN32)
ssize_t n;
uint8_t te = 1;
if (builder.stay)
return;
lwsl_notice("%s: idle period ended...\n", __func__);
if (builder.power_off_type &&
!strcmp(builder.power_off_type, "suspend")) {
lwsl_notice("%s: requesting suspend...\n", __func__);
n = write(lws_spawn_get_fd_stdxxx(lsp_suspender, 0), &te, 1);
if (n == 1) {
#if defined(WIN32)
Sleep(2000);
#else
sleep(2);
#endif
/*
* There were 0 tasks ongoing for us to suspend, start off
* with the same assumption and set the idle grace time
*/
lws_sul_schedule(builder.context, 0, &builder.sul_idle,
sul_idle_cb, SAI_IDLE_GRACE_US);
lwsl_notice("%s: resuming after suspend\n", __func__);
} else
lwsl_err("%s: failed to request suspend\n", __func__);
return;
}
/*
* Do we have a url for sai-power? If not, nothing we can do.
*/
if (!builder.url_sai_power)
return;
/*
* The plan is ask sai-power to turn us off... we don't know our
* dependency situation since we're just a standalone builder.
*
* We will have to let sai-power figure the deps out and say if
* it's willing to auto power-off or not.
*/
lwsl_notice("%s: creating sai-power ss...\n", __func__);
if (lws_ss_create(builder.context, 0, &ssi_saib_power_link_t,
(void *)builder.host, NULL, NULL, NULL)) {
lwsl_err("%s: failed to create sai-power ss\n", __func__);
return;
}
#endif
}
static lws_state_notify_link_t * const app_notifier_list[] = {
&nl, NULL
};
#if !defined(LWS_WITHOUT_EXTENSIONS)
static const struct lws_extension extensions[] = {
{
"permessage-deflate",
lws_extension_callback_pm_deflate,
"permessage-deflate"
"; client_no_context_takeover"
"; client_max_window_bits"
},
{ NULL, NULL, NULL /* terminator */ }
};
#endif
void sigint_handler(int sig)
{
interrupted = 1;
}
void
sai_ns_destroy(struct sai_nspawn *ns)
{
lws_dll2_remove(&ns->list);
free(ns);
}
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
struct stat sb;
const char *p;
if ((p = lws_cmdline_option(argc, argv, "--home")))
/*
* This is the deletion worker process being spawned, it only
* needs to know the home dir to clean up inside
*/
return sai_deletion_worker(p);
if ((p = lws_cmdline_option(argc, argv, "-s"))) {
ssize_t n = 0;
printf("%s: Spawn process creation entry...\n", __func__);
/*
* A new process gets started with this option before we drop
* privs. This allows us to suspend with root privs later.
*
* We just wait until we get a byte on stdin from the main
* process indicating we should suspend.
*/
while (n >= 0) {
#if !defined(WIN32)
int status;
pid_t p;
#endif
uint8_t d;
n = read(0, &d, 1);
lwsl_notice("%s: suspend process read returned %d\n", __func__, (int)n);
if (n <= 0)
continue;
if (d == 2) {
lwsl_warn("%s: suspend process ending\n", __func__);
break;
}
#if defined(WIN32)
/*
* On Windows, we can use the shutdown command.
* For suspend, we can use rundll32.
*/
switch(d) {
case 0:
system("shutdown /s /t 0");
break;
case 1:
system("rundll32.exe powrprof.dll,SetSuspendState 0,1,0");
break;
case 3:
if (builder.rebuild_script_user &&
builder.rebuild_script_root) {
if (system(builder.rebuild_script_user) == 0)
system(builder.rebuild_script_root);
}
break;
}
#else
p = fork();
if (!p)
switch(d) {
case 0:
#if defined(__NetBSD__)
execl("/sbin/shutdown", "/sbin/shutdown", "-h", "now", NULL);
#else
execl("/usr/sbin/shutdown", "/usr/sbin/shutdown", "--halt", "now", NULL);
#endif
break;
case 1:
execl("/usr/bin/systemctl", "/usr/bin/systemctl", "suspend", NULL);
break;
case 3:
if (builder.rebuild_script_user &&
builder.rebuild_script_root) {
char cmd[8192];
char user_buf[64];
const char *user = "sai";
int ret;
if (builder.perms) {
lws_strncpy(user_buf, builder.perms, sizeof(user_buf));
if (strchr(user_buf, ':'))
*strchr(user_buf, ':') = '\0';
user = user_buf;
}
lws_snprintf(cmd, sizeof(cmd),
"su - %s -c '%s'",
user,
builder.rebuild_script_user);
ret = system(cmd);
if (ret == 0) {
lws_snprintf(cmd, sizeof(cmd),
"PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin %s",
builder.rebuild_script_root);
system(cmd);
}
}
break;
}
else
waitpid(p, &status, 0);
#endif
}
lwsl_notice("%s: exiting suspend process\n", __func__);
return 0;
}
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_builder.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\\builder", temp);
config_dir = stg_config_dir;
CoTaskMemFree(wdi);
}
#endif
/*
* Let's parse the global bits out of the config
*/
lwsl_notice("%s: config dir %s\n", __func__, config_dir);
if (saib_config_global(&builder, config_dir)) {
lwsl_err("%s: global config failed\n", __func__);
return 1;
}
/*
* We need to sample the true uid / gid we should use inside
* the mountpoint for sai:nobody or sai:sai, by looking at
* what the uid and gid are on /home/sai before anything changes
* it
*/
if (stat(builder.home, &sb)) {
lwsl_err("%s: Can't find %s\n", __func__, builder.home);
return 1;
}
#if !defined(WIN32)
{
int pfd[2];
pid_t pid;
if (pipe(pfd) == -1) {
lwsl_err("pipe() failed\n");
return 1;
}
pid = fork();
if (pid == -1) {
lwsl_err("fork() failed\n");
return 1;
}
if (!pid) {
/* child: deletion worker */
char home_arg[256];
lws_snprintf(home_arg, sizeof(home_arg), "--home=%s",
builder.home);
close(pfd[1]); /* wr */
if (dup2(pfd[0], 0) < 0)
return 1;
close(pfd[0]);
execlp(argv[0], argv[0], home_arg, "--delete-worker", (char *)NULL);
lwsl_err("execlp failed\n");
return 1;
}
/* parent */
close(pfd[0]); /* rd */
builder.pipe_master_wr = pfd[1];
}
#else
{
char cmdline[512];
HANDLE hChildStd_IN_Rd = NULL;
HANDLE hChildStd_IN_Wr = NULL;
SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi;
STARTUPINFOA si;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
if (!CreatePipe(&hChildStd_IN_Rd, &hChildStd_IN_Wr, &sa, 0)) {
lwsl_err("CreatePipe failed\n");
return 1;
}
if (!SetHandleInformation(hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) {
lwsl_err("SetHandleInformation failed\n");
return 1;
}
memset(&pi, 0, sizeof(pi));
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.hStdInput = hChildStd_IN_Rd;
si.dwFlags |= STARTF_USESTDHANDLES;
lws_snprintf(cmdline, sizeof(cmdline), "%s --delete-worker --home=%s",
argv[0], builder.home);
if (!CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, 0,
NULL, NULL, &si, &pi)) {
lwsl_err("CreateProcess failed\n");
return 1;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hChildStd_IN_Rd);
builder.pipe_master_wr_win = hChildStd_IN_Wr;
}
#endif
#if defined(__linux__)
/*
* At this point we're still root. So we should be able
* to register our toplevel cgroup OK
*/
{
struct passwd *pwd = getpwuid(sb.st_uid);
struct group *grp = getgrgid(sb.st_gid);
if (lws_spawn_prepare_self_cgroup(pwd->pw_name, grp->gr_name)) {
lwsl_err("%s: failed to initialize cgroup dir %s %s\n", __func__, pwd->pw_name, grp->gr_name);
return 1;
}
}
#endif
#if !defined(__linux__) && !defined(WIN32)
/* we are still root */
mkdir(UDS_PATHNAME_LOGPROXY, 0700);
chown(UDS_PATHNAME_LOGPROXY, sb.st_uid, sb.st_gid);
mkdir(UDS_PATHNAME_RESPROXY, 0700);
chown(UDS_PATHNAME_RESPROXY, sb.st_uid, sb.st_gid);
#endif
/* if we don't do this, libgit2 looks in /root/.gitconfig */
#if defined(WIN32)
_putenv_s("HOME", builder.home);
#else
setenv("HOME", builder.home, 1);
#endif
lwsl_user("Sai Builder - "
"Copyright (C) 2019-2020 Andy Green <andy@warmcat.com>\n");
lwsl_user(" sai-builder [-c <config-file>]\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;
info.uid = sb.st_uid;
info.gid = sb.st_gid;
#if !defined(LWS_WITHOUT_EXTENSIONS)
if (!lws_cmdline_option(argc, argv, "-n"))
info.extensions = extensions;
#endif
info.pt_serv_buf_size = 32 * 1024;
info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
LWS_SERVER_OPTION_VALIDATE_UTF8 |
LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
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-builder";
nl.notify_cb = app_system_state_nf;
info.register_notifier_list = app_notifier_list;
/* create the lws context */
builder.context = lws_create_context(&info);
if (!builder.context) {
lwsl_err("lws init failed\n");
return 1;
}
/* ... and our vhost... */
pvo1a.value = builder.metrics_uri;
pvo1b.value = builder.metrics_path;
pvo1c.value = builder.metrics_secret;
info.pvo = &pvo1;
builder.vhost = lws_create_vhost(builder.context, &info);
if (!builder.vhost) {
lwsl_err("Failed to create tls vhost\n");
goto bail;
}
#if !defined(WIN32)
{
struct lws_spawn_piped_info info;
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));
info.vh = builder.vhost;
info.exec_array = ea;
info.max_log_lines = 100;
info.opaque = (void *)&builder.suspend_nspawn;
lsp_suspender = lws_spawn_piped(&info);
if (!lsp_suspender)
lwsl_notice("%s: suspend spawn failed\n", __func__);
/*
* We start off idle, with no tasks on any platform and doing
* the grace time before suspend. If tasks appear, the grace
* time will get cancelled.
*/
lws_sul_schedule(builder.context, 0, &builder.sul_idle,
sul_idle_cb, SAI_IDLE_GRACE_US);
}
#endif
while (!lws_service(builder.context, 0) && !interrupted)
;
bail:
if (lsp_suspender) {
uint8_t te = 2;
/*
* Clean up after the suspend process
*/
write(lws_spawn_get_fd_stdxxx(lsp_suspender, 0), &te, 1);
}
/* destroy the unique servers */
lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
builder.sai_plat_server_owner.head) {
struct sai_plat_server *cm = lws_container_of(p,
struct sai_plat_server, list);
lws_dll2_remove(&cm->list);
lws_ss_destroy(&cm->ss);
} lws_end_foreach_dll_safe(p, p1);
lws_start_foreach_dll_safe(struct lws_dll2 *, mp, mp1,
builder.sai_plat_owner.head) {
struct sai_plat *sp = lws_container_of(mp, struct sai_plat,
sai_plat_list);
lws_dll2_remove(&sp->sai_plat_list);
lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
sp->nspawn_owner.head) {
struct sai_nspawn *ns = lws_container_of(p,
struct sai_nspawn, list);
sai_ns_destroy(ns);
} lws_end_foreach_dll_safe(p, p1);
} lws_end_foreach_dll_safe(mp, mp1);
saib_config_destroy(&builder);
lws_sul_cancel(&builder.sul_idle);
lws_context_destroy(builder.context);
return 0;
}
|