parsav  parsav.t at [419d1a1ebe]

File parsav.t artifact 41c6f93980 part of check-in 419d1a1ebe


-- vim: ft=terra

local util = dofile('common.lua')
local buildopts, buildargs = util.parseargs{...}
config = dofile('config.lua')

lib = {
	init = {};
	load = function(lst)
		for _, l in pairs(lst) do
			local path = {}
			for m in l:gmatch('([^:]+)') do path[#path+1]=m end
			local tgt = lib
			for i=1,#path-1 do
				if tgt[path[i]] == nil then tgt[path[i]] = {} end
				tgt = tgt[path[i]]
			end
			tgt[path[#path]] = terralib.loadfile(l:gsub(':','/') .. '.t')()
		end
	end;
	loadlib = function(name,hdr)
		local p = config.pkg[name]
		-- for _,v in pairs(p.dylibs) do
		-- 	terralib.linklibrary(p.libdir .. '/' .. v)
		-- end
		return terralib.includec(p.incdir .. '/' .. hdr)
	end;
	dispatch = function(tbl)
		return macro(function(v,...)
			for ty,fn in pairs(tbl) do
				if v.tree.type == ty then return fn(v,...) end
			end
			return (tbl[false])(v,...)
		end)
	end;
	emit_unitary = function(nl,fd,...)
		local code = {}
		for i,v in ipairs{...} do
			if type(v) == 'string' or type(v) == 'number' then
				local str = tostring(v)
				code[#code+1] = `lib.io.send(2, str, [#str])
			elseif type(v) == 'table' and #v == 2 then
				code[#code+1] = `lib.io.send(2, [v[1]], [v[2]])
			elseif v.tree:is 'constant' then
				local str = tostring(v:asvalue())
				code[#code+1] = `lib.io.send(2, str, [#str])
			else
				code[#code+1] = quote var n = v in
					lib.io.send(2, n, lib.str.sz(n)) end
			end
		end
		if nl then code[#code+1] = `lib.io.send(fd, '\n', 1) end
		return code
	end;
	emitv = function(nl,fd,...)
		local vec = {}
		local defs = {}
		for i,v in ipairs{...} do
			local str, ct
			if type(v) == 'table' and v.tree and not (v.tree:is 'constant') then
				if v.tree.type.convertible == 'tuple' then
					str = `v._0
					ct = `v._1
				else
					local n = symbol(v.tree.type)
					defs[#defs + 1] = quote var [n] = v end
					str = n
					ct = `lib.str.sz(n)
				end
			else
				if type(v) == 'string' or type(v) == 'number' then
					str = tostring(v) 
				else--if v.tree:is 'constant' then
					str = tostring(v:asvalue())
				end
				ct = ct or #str
			end
			vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque](str), iov_len = ct}
		end
		if nl then vec[#vec + 1] = `[lib.uio.iovec]{iov_base = [&opaque]('\n'), iov_len = 1} end
		return quote
			[defs]
			var strs = array( [vec] )
		in lib.uio.writev(fd, strs, [#vec]) end
	end;
	trn = macro(function(cond, i, e)
		return quote
			var c: bool = [cond]
			var r: i.tree.type
			if c == true then r = i else r = e end
		in r end
	end);
	coalesce = macro(function(...)
		local args = {...}
		local ty = args[1].tree.type
		local val = symbol(ty)
		local empty if ty.type == 'integer'
			then empty = `0
			else empty = `nil
		end
		local exp = quote val = [empty] end

		for i=#args, 1, -1 do
			local v = args[i]
			exp = quote
				if [v] ~= [empty]
					then val = v
					else [exp]
				end
			end
		end

		local q = quote
			var [val]
			[exp]
		in val end
		return q
	end);
	proc = {
		exit = terralib.externfunction('exit', int -> {});
		getenv = terralib.externfunction('getenv', rawstring -> rawstring);
	};
	io = {
		send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff);
		recv = terralib.externfunction('read',  {int, rawstring, intptr} -> ptrdiff);
		say = macro(function(msg) return `lib.io.send(2, msg, [#(msg:asvalue())]) end);
		fmt = terralib.externfunction('printf',
			terralib.types.funcpointer({rawstring},{int},true));
	};
	str = { sz = terralib.externfunction('strlen', rawstring -> intptr) };
	copy = function(tbl)
		local new = {}
		for k,v in pairs(tbl) do new[k] = v end
		setmetatable(new, getmetatable(tbl))
		return new
	end;
	osclock = terralib.includec 'time.h';
}
if config.posix then
	lib.uio = terralib.includec 'sys/uio.h';
	lib.emit = lib.emitv -- use more efficient call where available
else lib.emit = lib.emit_unitary end

local starttime = global(lib.osclock.time_t)
local lastnoisetime = global(lib.osclock.time_t)
local noise = global(uint8,1)
local noise_header = function(code,txt,mod)
	if mod then
		return string.format('\27[%s;1m(%s %s)\27[m ', code,mod,txt)
	else
		return string.format('\27[%s;1m(%s)\27[m ', code,txt)
	end
end

local terra timehdr()
	var now = lib.osclock.time(nil)
	var diff = now - lastnoisetime
	if diff > 30 then -- print cur time
		lastnoisetime = now
		var curtime: int8[26]
		lib.osclock.ctime_r(&now, &curtime[0])
		for i=0,26 do if curtime[i] == @'\n' then curtime[i] = 0 break end end -- :/
		[ lib.emit(false, 2, '\27[1m[', `&curtime[0], ']\27[;36m\n +00 ') ]
	else -- print time since last msg
		var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0)
		[ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ]
	end
end

local defrep = function(level,n,code)
	return macro(function(...)
		local q = lib.emit(true, 2, noise_header(code,n), ...)
		return quote if noise >= level then timehdr(); [q] end end
	end);
end
lib.dbg = defrep(3,'debug', '32')
lib.report = defrep(2,'info', '35')
lib.warn = defrep(1,'warn', '33')
lib.bail = macro(function(...)
	local q = lib.emit(true, 2, noise_header('31','fatal'), ...)
	return quote
		timehdr(); [q]
		lib.proc.exit(1)
	end
end);
lib.stat = terralib.memoize(function(ty)
	local n = struct {
		ok: bool
		union {
			error: uint8
			val: ty
		}
	}
	n.name = string.format("stat<%s>", ty.name)
	n.stat_basetype = ty
	return n
end)
lib.enum = function(tbl)
	local ty = uint8
	if #tbl >= 2^32 then ty = uint64 -- hey, can't be too safe
	elseif #tbl >= 2^16 then ty = uint32
	elseif #tbl >= 2^8 then ty = uint16 end
	local o = { t = ty }
	for i, name in ipairs(tbl) do
		o[name] = i
	end
	return o
end
lib.set = function(tbl)
	local bytes = math.ceil(#tbl / 8)
	local o = {}
	for i, name in ipairs(tbl) do o[name] = i end
	local struct set { _store: uint8[bytes] }
	local struct bit { _v: intptr _set: &set}
	terra set:clear() for i=0,bytes do self._store[i] = 0 end end
	terra set:fill() for i=0,bytes do self._store[i] = 0xFF end end
	set.members = tbl
	set.name = string.format('set<%s>', table.concat(tbl, '|'))
	set.metamethods.__entrymissing = macro(function(val, obj)
		if o[val] == nil then error('value ' .. val .. ' not in set') end
		return `bit { _v=[o[val] - 1], _set = &obj }
	end)
	set.methods.dump = macro(function(self)
		local q = quote lib.io.say('dumping set:\n') end
		for i,v in ipairs(tbl) do
			q = quote
				[q]
				if [bool](self.[v])
					then lib.io.say([' - ' .. v .. ': true\n'])
					else lib.io.say([' - ' .. v .. ': false\n'])
				end
			end
		end
		return q
	end)
	set.metamethods.__add = macro(function(self,other)
		local new = symbol(set)
		local q = quote var [new] new:clear() end
		for i = 0, bytes - 1 do
			q = quote [q]
				new._store[i] = self._store[i] or other._store[i]
			end
		end
		return quote [q] in new end
	end)
	bit.metamethods.__cast = function(from,to,e)
		local q = quote var s = e
			in (s._set._store[s._v/8] and (1 << s._v % 8)) end
		if to == bit then error('casting to bit is not meaningful')
		elseif to == bool then return `([q] ~= 0)
		elseif to:isintegral() then return q
		elseif from == bit then error('cannot cast bit to ' .. tostring(to))
		else return nil end
	end
	bit.metamethods.__apply = terra(self: &bit): bool return @self end
	bit.metamethods.__lshift = terra(self: &bit, hl: bool)
		var byte = self._v / 8
		var bit = self._v % 8
		if hl then
			self._set._store[byte] = self._set._store[byte] or (1 << bit)
		else
			self._set._store[byte] = self._set._store[byte] and not (1 << bit)
		end
	end
	return set
end

lib.err = lib.loadlib('mbedtls','mbedtls/error.h')
lib.rsa = lib.loadlib('mbedtls','mbedtls/rsa.h')
lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h')
lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
lib.net = lib.loadlib('mongoose','mongoose.h')
lib.pq = lib.loadlib('libpq','libpq-fe.h')

lib.load {
	'mem',  'math', 'str', 'file', 'crypt';
	'http', 'session', 'tpl', 'store';
}

local be = {}
for _, b in pairs(config.backends) do
	be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')()
end
lib.store.backends = global(`array([be]))

lib.cmdparse = terralib.loadfile('cmdparse.t')()

do local collate = function(path,f, ...)
	return loadfile(path..'/'..f..'.lua')(path, ...)
end
data = {
	view = collate('view','load');
	static = {};
	stmap = global(lib.mem.ref(int8)[#config.embeds]); -- array of pointers to static content
} end
for i,e in ipairs(config.embeds) do local v = e[1]
	local fh = io.open('static/' .. v,'r')
	if fh == nil then error('static file ' .. v .. ' missing') end
	data.static[v] = fh:read('*a') fh:close()
end

-- slightly silly -- because we're using plain lua to gather up
-- the template sources, they have to be actually turned into
-- templates in the terra code, so we "mutate" them here
for k,v in pairs(data.view) do
	local t = lib.tpl.mk { body = v, id = 'view/'..k }
	data.view[k] = t
end

lib.load {
	'srv';
	'render:profile';
	'render:userpage';
	'route';
}

do
	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
	terra version() lib.io.send(1, p, [#p]) end
end

terra noise_init()
	starttime = lib.osclock.time(nil)
	lastnoisetime = 0
	var n = lib.proc.getenv('parsav_noise')
	if n ~= nil then
		if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then
			noise = n[0] - 0x30
			return
		end
	end
	noise = 1
end

local options = lib.cmdparse {
	version = {'V', 'display information about the binary build and exit'};
	quiet = {'q', 'do not print to standard out'};
	help = {'h', 'display this list'};
	backend_file = {'b', 'init from specified backend file', 1};
	static_dir = {'S', 'directory with overrides for static content', 1};
	builtin_data = {'B', 'do not load static content overrides at runtime under any circumstances'};
}


local static_setup = quote end
local mapin = quote end
local odir = symbol(rawstring)
local pathbuf = symbol(lib.str.acc)
for i,e in ipairs(config.embeds) do local v = e[1]
	local d = data.static[v]
	static_setup = quote [static_setup]
		([data.stmap])[([i-1])] = ([lib.mem.ref(int8)] { ptr = [d], ct = [#d] })
	end
	mapin = quote [mapin]
		var osz = pathbuf.sz
		pathbuf:push([v],[#v])
		var f = lib.file.open(pathbuf.buf, [lib.file.mode.read])
		if f.ok then defer f.val:close()
			var map = f.val:mapin(0,0) -- currently maps are leaked, maybe do something more elegant in future
			lib.report('loading static override content from ', pathbuf.buf)
			([data.stmap])[([i-1])] = ([lib.mem.ref(int8)] {
				ptr = [rawstring](map.addr);
				ct = map.sz;
			})
		end
		pathbuf.sz = osz
	end
end
local terra static_init(mode: &options)
	[static_setup]
	if mode.builtin_data then return end

	var [odir] = lib.proc.getenv('parsav_override_dir')
	if mode.static_dir ~= nil then
		odir=@mode.static_dir
	end
	if odir == nil then return end

	var [pathbuf] defer pathbuf:free()
	pathbuf:compose(odir,'/')
	[mapin]
end

terra entry(argc: int, argv: &rawstring): int
	if argc < 1 then lib.bail('bad invocation!') end

	noise_init()
	[lib.init]

	-- shut mongoose the fuck up
	lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil)
	var srv: lib.srv.overlord

	do var mode: options
		mode:parse(argc,argv) defer mode:free()
		static_init(&mode)
		if mode.version then version() return 0 end
		if mode.help then
			lib.io.send(1,  [options.helptxt], [#options.helptxt])
			return 0
		end
		var cnf: rawstring
		if mode.backend_file ~= nil
			then cnf = @mode.backend_file
			else cnf = lib.proc.getenv('parsav_backend_file')
		end
		if cnf == nil then cnf = "backend.conf" end

		srv:start(cnf)
	end

	lib.report('listening for requests')
	while true do
		srv:poll()
	end
	srv:shutdown()

	return 0
end

local bflag = function(long,short)
	if short and util.has(buildopts, short) then return true end
	if long and util.has(buildopts, long) then return true end
	return false
end

if bflag('dump-config','C') then
	print(util.dump(config))
	os.exit(0)
end

local holler = print
local out = config.exe and 'parsav' or 'parsav.o'
local linkargs = {}

if bflag('quiet','q') then holler = function() end end
if bflag('asan','s') then linkargs[#linkargs+1] = '-fsanitize=address' end
if bflag('lsan','S') then linkargs[#linkargs+1] = '-fsanitize=leak' end

if config.posix then
	linkargs[#linkargs+1] = '-pthread'
end
for _,p in pairs(config.pkg) do util.append(linkargs, p.linkargs) end
holler('linking with args',util.dump(linkargs))
terralib.saveobj(out, {
		main = entry
	},
	linkargs,
	config.tgttrip and terralib.newtarget {
		Triple = config.tgttrip;
		CPU = config.tgtcpu;
		FloatABIHard = config.tgthf;
	} or nil)