beterraba

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();
	};
};