ref: master
cmd/beterrabamon/main.ha
use log; use beterraba; use net::unix; use net; use fs; use os; use fmt; use io; use errors; use path; use dirs; use os::exec; use os::exec::{signal}; use time; use strings; export fn main() void = { const name = os::args[1]; const conn = conn(); defer io::close(conn)!; prepare(); const svc = retrieve(&conn, name); let pid = boot(&svc); announce(&conn, "started", svc.name); for (true) { const exit_status = match (exec::wait(&pid)) { case let s: exec::status => yield s; case let err: exec::error => // TODO: End gracefully when daemon dies log::printfln("Something went wrong on wait"); break; }; match (exec::exit(&exit_status)) { case let s: exec::exited => announce(&conn, "stopped", svc.name); log::printfln("Exited with status {}", exec::exitstr(s)); case let s: exec::signaled => if (s == signal::SIGSEGV) { announce(&conn, "crashed", svc.name); log::printfln("We need to restart"); time::sleep(1 * time::SECOND); // TODO: Announce that this service has crashed pid = boot(&svc); } else { // TODO: Store the exit status of the service announce(&conn, "crashed", svc.name); log::printfln("Died of {}", exec::exitstr(s)); }; }; //wait(&pid); }; }; // Create the directory suitable for storing state from this monitor // it should also setup a logger to that place, and redirect the stdout/stderr // from the command that we're monitoring to that log via os::exec::pipe. Later // on, we'll pipe the stdout/stderr from the monitor itself (so messages such as // failures or anything non-essential) to the daemon log (so technically the // daemon will have to grab the stdout/stderr from the monitor and just pipe to // it's own logger. In the end we'll have the following structure: // // daemon -> /var/log/beterrabad/daemon.log // |------- monitor -> /var/log/beterrabad/daemon.log // |------- cmd -> /var/log/beterrabad/monitors/cmd.log // // (and so on) // fn prepare() void = { // TODO: Implement the above //fs::mkdir log::printfln("IMPLEMENT"); }; fn conn() net::socket = { let buf = path::init(); // TODO: Bubble up dirs::runtime errors const sockpath = path::set(&buf, dirs::runtime()!, "beterrabad")!; let conn = match (unix::connect(sockpath)) { case let s: net::socket => yield s; case errors::noentry => log::fatal("error: baterraba connection failed (is it running?)"); case let e: net::error => log::fatal("error:", net::strerror(e)); }; return conn; }; // TODO: Move socket stuff into it's own file // Retrieve a service from the daemon given it's name fn retrieve(conn: *net::socket, name: str) beterraba::service = { log::println("Begin retrieving"); let buf = path::init(); // TODO: Bubble up dirs::runtime errors const servname = fmt::asprintf("{}.service", name); defer free(servname); const servpath = path::set(&buf, dirs::config("beterraba"), servname)!; log::println("Service name: ", servpath); match(os::open(servpath)) { case let f: io::file => return beterraba::parse(f); case let e: fs::error => log::fatal("Failed to retrieve the service (moved/deleted or wrong name?)"); }; }; // Announce info regarding a given service fn announce(sock: *io::file, ops: str, name: str) void = { // TODO: Move into a list of valid ops write((*sock), "started", name); }; // Just forks & starts the given service fn boot(svc: *beterraba::service) int = { let pid = 0; match (exec::fork()) { case let childpid: int => pid = childpid; log::printfln("Starting process with PID {}", pid); case void => start(svc); }; return pid; }; // Starts a given set of commands with their args fn start(svc: *beterraba::service) void = { const cmd = exec::cmd(svc.definition.cmd, svc.definition.args); match (cmd) { case let cmddef: exec::command => exec::exec(&cmddef); case exec::nocmd => log::printfln("Couldn't build cmd"); abort(); case exec::error => // TODO: Return and inform the thing log::printfln("Couldn't execute cmd {}, error"); abort(); case => log::println("Something went terrible wrong, good luck!"); abort(); }; };