Overview
| Comment: | iterating |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
59e1d7d56aea6174c52dbedafd74e94b |
| User & Date: | lexi on 2020-12-16 08:46:02 |
| Other Links: | manifest | tags |
Context
|
2020-12-16
| ||
| 08:46 | add nix build file check-in: df4ae251ef user: lexi tags: trunk | |
| 08:46 | iterating check-in: 59e1d7d56a user: lexi tags: trunk | |
|
2020-12-14
| ||
| 14:40 | more boilerplate, add template framework check-in: 6f17de4767 user: lexi tags: trunk | |
Changes
Added backend/pgsql.t version [0360541ecf].
1 +-- vim: ft=terra 2 +local queries = { 3 + conf_get = { 4 + params = {rawstring}, sql = [[ 5 + select value from parsav_config 6 + where key = $1::text limit 1 7 + ]]; 8 + }; 9 + 10 + conf_set = { 11 + params = {rawstring,rawstring}, sql = [[ 12 + insert into parsav_config (key, value) 13 + values ($1::text, $2::text) 14 + on conflict (key) do update set value = $2::text 15 + ]]; 16 + }; 17 + 18 + conf_reset = { 19 + params = {rawstring}, sql = [[ 20 + delete from parsav_config where 21 + key = $1::text 22 + ]]; 23 + }; 24 + 25 + actor_fetch_uid = { 26 + params = {uint64}, sql = [[ 27 + select 28 + id, nym, handle, origin, 29 + bio, rank, quota, key 30 + from parsav_actors 31 + where id = $1::bigint 32 + ]]; 33 + }; 34 + 35 + actor_fetch_xid = { 36 + params = {rawstring}, sql = [[ 37 + select a.id, a.nym, a.handle, a.origin, 38 + a.bio, a.rank, a.quota, a.key, 39 + 40 + coalesce(s.domain, 41 + (select value from parsav_config 42 + where key='domain' limit 1)) as domain 43 + 44 + from parsav_actors as a 45 + left join parsav_servers as s 46 + on a.origin = s.id 47 + 48 + where $1::text = (a.handle || '@' || domain) or 49 + $1::text = ('@' || a.handle || '@' || domain) or 50 + (a.origin is null and $1::text = ('@' || a.handle)) 51 + ]]; 52 + }; 53 +} 54 + 55 +local struct pqr { 56 + sz: intptr 57 + res: &lib.pq.PGresult 58 +} 59 +terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end 60 +terra pqr:null(row: intptr, col: intptr) 61 + return (lib.pq.PQgetisnull(self.res, row, col) == 1) 62 +end 63 +terra pqr:string(row: intptr, col: intptr) 64 + var v = lib.pq.PQgetvalue(self.res, row, col) 65 + var r: lib.mem.ptr(int8) 66 + r.ct = lib.str.sz(v) 67 + r.ptr = lib.str.ndup(v, r.ct) 68 + return r 69 +end 70 +pqr.methods.int = macro(function(self, ty, row, col) 71 + return quote 72 + var i: ty:astype() 73 + var v = lib.pq.PQgetvalue(self.res, row, col) 74 + lib.math.netswap_ip(ty, v, &i) 75 + in i end 76 +end) 77 + 78 +local con = symbol(&lib.pq.PGconn) 79 +local prep = {} 80 +for k,q in pairs(queries) do 81 + local qt = (q.sql):gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1') 82 + local stmt = 'parsavpg_' .. k 83 + prep[#prep + 1] = quote 84 + var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil) 85 + defer lib.pq.PQclear(res) 86 + if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then 87 + if res == nil then 88 + lib.bail('grievous error occurred preparing ',k,' statement') 89 + end 90 + lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res)) 91 + end 92 + lib.dbg('prepared PGSQL statement ',k) 93 + end 94 + 95 + local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {} 96 + for i, ty in ipairs(q.params) do 97 + args[i] = symbol(ty) 98 + ft[i] = `1 99 + if ty == rawstring then 100 + counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]])) 101 + casts[i] = `[&int8]([args[i]]) 102 + elseif ty:isintegral() then 103 + counters[i] = ty.bytes 104 + casts[i] = `[&int8](&[args[i]]) 105 + fixers[#fixers + 1] = quote 106 + --lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]]) 107 + [args[i]] = lib.math.netswap(ty, [args[i]]) 108 + end 109 + end 110 + end 111 + 112 + q.exec = terra(src: &lib.store.source, [args]) 113 + var params = arrayof([&int8], [casts]) 114 + var params_sz = arrayof(int, [counters]) 115 + var params_ft = arrayof(int, [ft]) 116 + [fixers] 117 + var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt, 118 + [#args], params, params_sz, params_ft, 1) 119 + if res == nil then 120 + lib.bail(['grievous error occurred executing '..k..' against database']) 121 + elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then 122 + lib.bail(['PGSQL database procedure '..k..' failed\n'], 123 + lib.pq.PQresultErrorMessage(res)) 124 + end 125 + 126 + var ct = lib.pq.PQntuples(res) 127 + if ct == 0 then 128 + lib.pq.PQclear(res) 129 + return pqr {0, nil} 130 + else 131 + return pqr {ct, res} 132 + end 133 + end 134 +end 135 + 136 +local terra row_to_actor(r: &pqr, row: intptr): lib.store.actor 137 + var a = lib.store.actor { 138 + id = r:int(uint64, row, 0); 139 + nym = r:string(row, 1); 140 + handle = r:string(row, 2); 141 + bio = r:string(row, 4); 142 + key = r:string(row, 7); 143 + rights = lib.store.rights_default(); 144 + } 145 + a.rights.rank = r:int(uint16, 0, 5); 146 + a.rights.quota = r:int(uint32, 0, 6); 147 + if r:null(0,3) then a.origin = 0 148 + else a.origin = r:int(uint64,0,3) end 149 + return a 150 +end 151 + 152 +local b = `lib.store.backend { 153 + id = "pgsql"; 154 + open = [terra(src: &lib.store.source): &opaque 155 + lib.report('connecting to postgres database: ', src.string.ptr) 156 + var [con] = lib.pq.PQconnectdb(src.string.ptr) 157 + if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then 158 + lib.warn('postgres backend connection failed') 159 + lib.pq.PQfinish(con) 160 + return nil 161 + end 162 + var res = lib.pq.PQexec(con, [[ 163 + select pg_catalog.set_config('search_path', 'public', false) 164 + ]]) 165 + if res ~= nil then defer lib.pq.PQclear(res) end 166 + if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then 167 + lib.warn('failed to secure postgres connection') 168 + lib.pq.PQfinish(con) 169 + return nil 170 + end 171 + 172 + [prep] 173 + return con 174 + end]; 175 + close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end]; 176 + 177 + conf_get = [terra(src: &lib.store.source, key: rawstring) 178 + var r = queries.conf_get.exec(src, key) 179 + if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else 180 + defer r:free() 181 + return r:string(0,0) 182 + end 183 + end]; 184 + conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring) 185 + queries.conf_set.exec(src, key, val):free() end]; 186 + conf_reset = [terra(src: &lib.store.source, key: rawstring) 187 + queries.conf_reset.exec(src, key):free() end]; 188 + 189 + actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64) 190 + var r = queries.actor_fetch_uid.exec(src, uid) 191 + if r.sz == 0 then 192 + return [lib.stat(lib.store.actor)] { ok = false, error = 1} 193 + else 194 + defer r:free() 195 + var a = [lib.stat(lib.store.actor)] { ok = true } 196 + a.val = row_to_actor(&r, 0) 197 + a.val.source = src 198 + return a 199 + end 200 + end]; 201 +} 202 + 203 +return b
Modified cmdparse.t from [8abc7a6fc7] to [c7f162fae3].
1 +-- vim: ft=terra 1 2 return function(tbl) 2 3 local options = terralib.types.newstruct('options') do 3 4 local flags = '' for _,d in pairs(tbl) do flags = flags .. d[1] end 4 5 local helpstr = 'usage: parsav [-' .. flags .. '] [<arg>...]\n' 5 6 options.entries = { 6 7 {field = 'arglist', type = lib.mem.ptr(rawstring)} 7 8 }
Modified config.lua from [85408f7c8a] to [60797f7bc4].
19 19 dist = default('parsav_dist', coalesce( 20 20 os.getenv('NIX_PATH') and 'nixos', 21 21 os.getenv('NIX_STORE') and 'nixos', 22 22 '')); 23 23 tgttrip = default('parsav_arch_triple'); -- target triple, used in xcomp 24 24 tgtcpu = default('parsav_arch_cpu'); -- target cpu, used in xcomp 25 25 tgthf = u.tobool(default('parsav_arch_armhf',true)); 26 + endian = default('parsav_arch_endian', 'little'); 26 27 build = { 27 28 id = u.rndstr(6); 28 29 release = u.ingest('release'); 29 30 when = os.date(); 30 31 }; 31 32 feat = {}; 32 33 }
Added file.t version [fc7770c3f7].
1 +-- vim: ft=terra 2 +-- TODO: add support for windows IO calls 3 +local handle_type = int 4 +local posix = terralib.includec 'fcntl.h' 5 +local unistd = terralib.includec 'unistd.h' 6 + 7 +struct file { 8 + handle: handle_type 9 + read: bool 10 + write: bool 11 +} 12 + 13 +file.mode = { read = 0, write = 1, rw = 2 } 14 +file.seek = { abs = 0, ofs = 1, eof = 2 } 15 + 16 +file.methods = { 17 + open = terra(path: rawstring, mode: uint8) 18 + var f: file 19 + var flag: int 20 + if mode == [file.mode.rw] then 21 + flag = posix.O_RDWR 22 + f.read = true f.write = true 23 + elseif mode == [file.mode.read] then 24 + flag = posix.O_RDONLY 25 + f.read = true f.write = false 26 + elseif mode == [file.mode.read] then 27 + flag = posix.O_WRONLY 28 + f.read = false f.write = true 29 + else lib.bail('invalid file mode') end 30 + lib.dbg('opening file ', path) 31 + f.handle = posix.open(path, flag) 32 + 33 + var r: lib.stat(file) 34 + if f.handle == -1 then 35 + r.ok = false 36 + r.error = 1 -- TODO get errno somehow? 37 + else 38 + r.ok = true 39 + r.val = f 40 + end 41 + return r 42 + end; 43 + close = terra(self: &file) 44 + unistd.close(self.handle) 45 + self.handle = -1 46 + self.read = false 47 + self.write = false 48 + end; 49 + read = terra(self: &file, dest: rawstring, sz: intptr): ptrdiff 50 + return unistd.read(self.handle,dest,sz) 51 + end; 52 + write = terra(self: &file, data: &opaque, sz: intptr): ptrdiff 53 + return unistd.write(self.handle,data,sz) 54 + end; 55 + seek = terra(self: &file, ofs: ptrdiff, wh: int) 56 + var whence: int 57 + if wh == [file.seek.abs] then 58 + whence = unistd.SEEK_SET 59 + elseif wh == [file.seek.ofs] then 60 + whence = unistd.SEEK_CUR 61 + elseif wh == [file.seek.eof] then 62 + whence = unistd.SEEK_END 63 + else lib.bail('invalid seek mode') end 64 + 65 + return unistd.lseek(self.handle, ofs, whence) 66 + end; 67 +} 68 + 69 +return file
Modified makefile from [0f98e9b572] to [40c8adaeb1].
18 18 lib/mbedtls/library/libmbedcrypto.a \ 19 19 lib/mbedtls/library/libmbedx509.a 20 20 dep.mongoose: lib/mongoose/libmongoose.a 21 21 dep.json-c: lib/json-c/libjson-c.a 22 22 23 23 lib: 24 24 mkdir $@ 25 -# parsav is designed to be fronted by a real web 26 -# server like nginx if SSL is to be used 27 25 # generate a shim static library so mongoose cooperates 28 -# with the build apparatus 26 +# with the build apparatus. note that parsav is designed 27 +# to be fronted by a real web server like nginx if SSL 28 +# is to be used, so we don't turn on SSL in mongoose 29 29 lib/mongoose/libmongoose.a: lib/mongoose lib/mongoose/mongoose.c lib/mongoose/mongoose.h 30 30 $(CC) -c $</mongoose.c -o lib/mongoose/mongoose.o \ 31 31 -DMG_ENABLE_THREADS \ 32 32 -DMG_ENABLE_IPV6 \ 33 33 -DMG_ENABLE_HTTP_WEBDAV \ 34 34 -DMG_ENABLE_HTTP_WEBSOCKET=0 35 35 ar rcs $@ lib/mongoose/*.o ................................................................................ 40 40 lib/json-c/libjson-c.a: lib/json-c/Makefile 41 41 $(MAKE) -C lib/json-c 42 42 lib/mbedtls/library/%.a: lib/mbedtls 43 43 $(MAKE) -C lib/mbedtls/library $*.a 44 44 45 45 ifeq ($(dl), git) 46 46 lib/mongoose: lib 47 - cd lib && git clone https://github.com/cesanta/mongoose 47 + cd lib && git clone https://github.com/cesanta/mongoose.git 48 48 lib/mbedtls: lib 49 49 cd lib && git clone https://github.com/ARMmbed/mbedtls.git 50 50 lib/json-c: lib 51 51 cd lib && git clone https://github.com/json-c/json-c.git 52 52 else 53 53 lib/%: lib/%.tar.gz 54 54 cd lib && tar zxf $*.tar.gz
Added math.t version [fe958b6645].
1 +-- vim: ft=terra 2 +local m = { 3 + shorthand = {maxlen = 14} 4 +} 5 + 6 +-- swap in place -- faster on little endian 7 +m.netswap_ip = macro(function(ty, src, dest) 8 + if ty:astype().type ~= 'integer' then error('bad type') end 9 + local bytes = ty:astype().bytes 10 + src = `[&uint8](src) 11 + dest = `[&uint8](dest) 12 + if config.endian == 'little' then 13 + return quote for i = 0, bytes do dest[i] = src[bytes - (i+1)] end end 14 + elseif config.endian == 'big' then 15 + return quote for i = 0, bytes do dest[i] = src[i] end end 16 + else error('unknown endianness '..config.endian) end 17 +end) 18 + 19 +-- swap out of place -- safer, more flexible, and optimized to an intrinsic call; trivial on big endian 20 +m.netswap = macro(function(tyq, src) 21 + if config.endian == 'little' then 22 + local ty = tyq:astype() 23 + local a,b = symbol(ty), symbol(ty) 24 + local bytes = ty.bytes 25 + local steps = {} 26 + for i=0,bytes-1 do 27 + steps[#steps + 1] = quote 28 + b = b << 8 29 + b = b or (a and 0xff) 30 + a = a >> 8 31 + end 32 + end 33 + return quote 34 + var [a] = src 35 + var [b] = 0 36 + [steps] 37 + in b end 38 + elseif config.endian == 'big' then return `src 39 + else error('unknown endianness '..config.endian) end 40 +end) 41 + 42 +terra m.shorthand.cval(character: int8): {uint8, bool} 43 + var ch = [uint8](character) 44 + 45 + if ch >= 0x30 and ch <= 0x39 then 46 + ch = 00 + (ch - 0x30) 47 + elseif ch == 0x2d then ch = 10 48 + elseif ch >= 0x41 and ch <= 0x5a then 49 + ch = 11 + (ch - 0x41) 50 + elseif ch == 0x3a then ch = 37 51 + elseif ch >= 0x61 and ch <= 0x7a then 52 + ch = 38 + (ch - 0x61) 53 + else return 0, false end 54 + 55 + return ch, true 56 +end 57 + 58 +terra m.shorthand.gen(val: uint64, dest: rawstring): ptrdiff 59 + var lst = "0123456789-ABCDEFGHIJKLMNOPQRSTUVWXYZ:abcdefghijklmnopqrstuvwxyz" 60 + var buf: int8[m.shorthand.maxlen] 61 + var ptr = [&int8](buf) 62 + while val ~= 0 do 63 + var v = val % 64 64 + @ptr = lst[v] 65 + ptr = ptr + 1 66 + val = val / 64 67 + end 68 + var len = ptr - buf 69 + for i = 0, len do 70 + dest[i] = buf[len - (i+1)] 71 + end 72 + dest[len] = 0 73 + return len 74 +end 75 + 76 +terra m.shorthand.parse(s: rawstring, len: intptr): {uint64, bool} 77 + var val: uint64 = 0 78 + for i = 0, len do 79 + var v, ok = m.shorthand.cval(s[i]) 80 + if ok == false then return 0, false end 81 + val = (val * 64) + v 82 + end 83 + return val, true 84 +end 85 + 86 +return m
Modified parsav.t from [889180c92d] to [ce122c09dc].
34 34 code[#code+1] = quote var n = v in 35 35 lib.io.send(2, n, lib.str.sz(n)) end 36 36 end 37 37 end 38 38 code[#code+1] = `lib.io.send(2, '\n', 1) 39 39 return code 40 40 end; 41 + trn = macro(function(cond, i, e) 42 + return quote 43 + var c: bool = [cond] 44 + var r: i.tree.type 45 + if c == true then r = i else r = e end 46 + in r end 47 + end); 41 48 proc = { 42 49 exit = terralib.externfunction('exit', int -> {}); 43 50 getenv = terralib.externfunction('getenv', rawstring -> rawstring); 44 51 }; 45 52 io = { 46 - open = terralib.externfunction('open', {rawstring, int} -> int); 47 - close = terralib.externfunction('close', {int} -> int); 48 53 send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff); 49 54 recv = terralib.externfunction('read', {int, rawstring, intptr} -> ptrdiff); 50 55 say = macro(function(msg) return `lib.io.send(2, msg, [#(msg:asvalue())]) end); 51 56 fmt = terralib.externfunction('printf', 52 57 terralib.types.funcpointer({rawstring},{int},true)); 53 58 }; 54 59 str = { 55 60 sz = terralib.externfunction('strlen', rawstring -> intptr); 56 61 cmp = terralib.externfunction('strcmp', {rawstring, rawstring} -> int); 62 + ncmp = terralib.externfunction('strncmp', {rawstring, rawstring, intptr} -> int); 57 63 cpy = terralib.externfunction('stpcpy',{rawstring, rawstring} -> rawstring); 58 64 ncpy = terralib.externfunction('stpncpy',{rawstring, rawstring, intptr} -> rawstring); 65 + ndup = terralib.externfunction('strndup',{rawstring, intptr} -> rawstring); 59 66 fmt = terralib.externfunction('asprintf', 60 67 terralib.types.funcpointer({&rawstring},{int},true)); 61 68 }; 69 + copy = function(tbl) 70 + local new = {} 71 + for k,v in pairs(tbl) do new[k] = v end 72 + setmetatable(new, getmetatable(tbl)) 73 + return new 74 + end; 62 75 mem = { 63 76 zero = macro(function(r) 64 77 return quote 65 78 for i = 0, [r.tree.type.N] do r[i] = 0 end 66 79 end 67 80 end); 68 81 heapa_raw = terralib.externfunction('malloc', intptr -> &opaque); ................................................................................ 76 89 ct = sz; 77 90 } 78 91 end) 79 92 }; 80 93 } 81 94 82 95 local noise = global(uint8,1) 96 +local noise_header = function(code,txt,mod) 97 + if mod then 98 + return string.format('\27[%s;1m(parsav::%s %s)\27[m ', code,mod,txt) 99 + else 100 + return string.format('\27[%s;1m(parsav %s)\27[m ', code,txt) 101 + end 102 +end 83 103 local defrep = function(level,n,code) 84 104 return macro(function(...) 85 - local q = lib.emit("\27["..code..";1m(parsav "..n..")\27[m ", ...) 105 + local q = lib.emit(noise_header(code,n), ...) 86 106 return quote 87 107 if noise >= level then [q] end 88 108 end 89 109 end); 90 110 end 91 111 lib.dbg = defrep(3,'debug', '32') 92 112 lib.report = defrep(2,'info', '35') 93 113 lib.warn = defrep(1,'warn', '33') 94 114 lib.bail = macro(function(...) 95 - local q = lib.emit("\27[31;1m(parsav fatal)\27[m ", ...) 115 + local q = lib.emit(noise_header('31','fatal'), ...) 96 116 return quote 97 117 [q] 98 118 lib.proc.exit(1) 99 119 end 100 120 end); 121 +lib.stat = terralib.memoize(function(ty) 122 + local n = struct { 123 + ok: bool 124 + union { 125 + error: uint8 126 + val: ty 127 + } 128 + } 129 + n.name = string.format("stat<%s>", ty.name) 130 + n.stat_basetype = ty 131 + return n 132 +end) 133 +lib.enum = function(tbl) 134 + local ty = uint8 135 + if #tbl >= 2^32 then ty = uint64 -- hey, can't be too safe 136 + elseif #tbl >= 2^16 then ty = uint32 137 + elseif #tbl >= 2^8 then ty = uint16 end 138 + local o = { t = ty } 139 + for i, name in ipairs(tbl) do 140 + o[name] = i 141 + end 142 + return o 143 +end 101 144 lib.mem.ptr = terralib.memoize(function(ty) 102 145 local t = terralib.types.newstruct(string.format('ptr<%s>', ty)) 103 146 t.entries = { 104 147 {'ptr', &ty}; 105 148 {'ct', intptr}; 106 149 } 150 + t.ptr_basetype = ty 107 151 local recurse = false 108 152 if ty:isstruct() then 109 153 if ty.methods.free then recurse = true end 110 154 end 111 155 t.methods = { 112 156 free = terra(self: &t): bool 113 157 [recurse and quote ................................................................................ 139 183 self.ct = newct 140 184 return true 141 185 else return false end 142 186 end; 143 187 } 144 188 return t 145 189 end) 190 +lib.mem.vec = terralib.memoize(function(ty) 191 + local v = terralib.types.newstruct(string.format('vec<%s>', ty.name)) 192 + v.entries = { 193 + {field = 'storage', type = lib.mem.ptr(ty)}; 194 + {field = 'sz', type = intptr}; 195 + {field = 'run', type = intptr}; 196 + } 197 + local terra biggest(a: intptr, b: intptr) 198 + if a > b then return a else return b end 199 + end 200 + terra v:assure(n: intptr) 201 + if self.storage.ct < n then 202 + self.storage:resize(biggest(n, self.storage.ct + self.run)) 203 + end 204 + end 205 + v.methods = { 206 + init = terra(self: &v, run: intptr): bool 207 + if not self.storage:init(run) then return false end 208 + self.run = run 209 + self.sz = 0 210 + return true 211 + end; 212 + new = terra(self: &v): &ty 213 + self:assure(self.sz + 1) 214 + self.sz = self.sz + 1 215 + return self.storage.ptr + (self.sz - 1) 216 + end; 217 + push = terra(self: &v, val: ty) 218 + self:assure(self.sz + 1) 219 + self.storage.ptr[self.sz] = val 220 + self.sz = self.sz + 1 221 + end; 222 + free = terra(self: &v) self.storage:free() end; 223 + last = terra(self: &v, idx: intptr): &ty 224 + if self.sz > idx then 225 + return self.storage.ptr + (self.sz - (idx+1)) 226 + else lib.bail('vector underrun!') end 227 + end; 228 + crush = terra(self: &v) 229 + self.storage:resize(self.sz) 230 + return self.storage 231 + end; 232 + } 233 + v.metamethods.__apply = terra(self: &v, idx: intptr): &ty -- no index?? 234 + if self.sz > idx then 235 + return self.storage.ptr + idx 236 + else lib.bail('vector overrun!') end 237 + end 238 + return v 239 +end) 146 240 147 241 lib.err = lib.loadlib('mbedtls','mbedtls/error.h') 148 242 lib.rsa = lib.loadlib('mbedtls','mbedtls/rsa.h') 149 243 lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h') 150 244 lib.md = lib.loadlib('mbedtls','mbedtls/md.h') 151 245 lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h') 152 246 lib.net = lib.loadlib('mongoose','mongoose.h') 153 247 lib.pq = lib.loadlib('libpq','libpq-fe.h') 248 +lib.file = terralib.loadfile('file.t')() 249 +lib.math = terralib.loadfile('math.t')() 154 250 lib.crypt = terralib.loadfile('crypt.t')() 155 251 lib.http = terralib.loadfile('http.t')() 156 252 lib.tpl = terralib.loadfile('tpl.t')() 157 253 lib.string = terralib.loadfile('string.t')() 158 254 lib.store = terralib.loadfile('store.t')() 255 + 256 +local be = {} 257 +for _, b in pairs { 'pgsql' } do 258 + be[#be+1] = terralib.loadfile('backend/' .. b .. '.t')() 259 +end 260 +lib.store.backends = global(`array([be])) 261 + 159 262 lib.cmdparse = terralib.loadfile('cmdparse.t')() 263 +lib.srv = terralib.loadfile('srv.t')() 160 264 161 265 do local collate = function(path,f, ...) 162 266 return loadfile(path..'/'..f..'.lua')(path, ...) 163 267 end 164 268 data = { 165 269 view = collate('view','load'); 166 270 } end ................................................................................ 181 285 lib.crypt.pem(pub, &kp, buf) 182 286 lib.io.send(1, msg, [#msg]) 183 287 lib.io.send(1, [rawstring](&buf), lib.str.sz([rawstring](&buf))) 184 288 lib.io.send(1, '\n', 1) 185 289 end 186 290 end) 187 291 188 -local handle = { 189 - http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque) 190 - switch event do 191 - case lib.net.MG_EV_HTTP_MSG then 192 - lib.dbg('routing HTTP request') 193 - var msg = [&lib.net.mg_http_message](p) 194 - 195 - end 196 - end 197 - end; 198 -} 199 292 do 200 293 local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when) 201 294 terra version() lib.io.send(1, p, [#p]) end 202 295 end 203 296 terra noise_init() 204 297 var n = lib.proc.getenv('parsav_noise') 205 298 if n ~= nil then ................................................................................ 217 310 help = {'h', 'display this list'} 218 311 } 219 312 220 313 terra entry(argc: int, argv: &rawstring): int 221 314 noise_init() 222 315 [lib.init] 223 316 317 + -- shut mongoose the fuck up 318 + lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil) 319 + 224 320 var mode: options 225 321 mode:parse(argc,argv) 226 322 if mode.version then 227 323 version() 228 324 return 0 229 325 end 230 326 if mode.help then 231 327 lib.io.send(1, [options.helptxt], [#options.helptxt]) 232 328 return 0 233 329 end 234 - 235 - var bind = lib.proc.getenv('parsav_bind') 236 - if bind == nil then bind = '[::]:10917' end 237 - 238 - var nm: lib.net.mg_mgr 239 - lib.net.mg_mgr_init(&nm) 240 - 241 - var nmc = lib.net.mg_http_listen(&nm, bind, handle.http, nil) 242 - 330 + var srv: lib.srv 331 + srv:start('backend.conf') 332 + lib.report('listening for requests') 243 333 while true do 244 - lib.net.mg_mgr_poll(&nm,1000) 334 + srv:poll() 245 335 end 336 + srv:shutdown() 246 337 247 - lib.net.mg_mgr_free(&nm) 248 338 return 0 249 339 end 250 340 251 341 local bflag = function(long,short) 252 342 if short and util.has(buildopts, short) then return true end 253 343 if long and util.has(buildopts, long) then return true end 254 344 return false
Added schema.sql version [2da83887bf].
1 +\prompt 'domain name: ' domain 2 +\prompt 'bind to socket: ' bind 3 +\qecho 'by default, parsav tracks rights on its own. you can override this later by replacing the rights table with a view, but you''ll then need to set appropriate rules on the view to allow administrators to modify rights from the web UI, or set the rights-readonly flag in the config table to true. for now, enter the name of an actor who will be granted full rights when she logs in.' 4 +\prompt 'admin actor: ' admin 5 +\qecho 'you will need to create an authentication view mapping your user database to something parsav can understand; see auth.sql for an example. enter the name of the view to use.' 6 +\prompt 'auth view: ' auth 7 + 8 +begin; 9 + 10 +drop table if exists parsav_config; 11 +create table if not exists parsav_config ( 12 + key text primary key, 13 + value text 14 +); 15 + 16 +insert into parsav_config (key,value) values 17 + ('bind',:'bind'), 18 + ('domain',:'domain'), 19 + ('auth-source',:'auth'), 20 + ('administrator',:'admin'); 21 + 22 +-- note that valid ids should always > 0, as 0 is reserved for null 23 +-- on the client side, vastly simplifying code 24 +drop table if exists parsav_servers cascade; 25 +create table parsav_servers ( 26 + id bigint primary key default (1+random()*(2^63-1))::bigint, 27 + domain text not null, 28 + key bytea 29 +); 30 +drop table if exists parsav_actors cascade; 31 +create table parsav_actors ( 32 + id bigint primary key default (1+random()*(2^63-1))::bigint, 33 + nym text, 34 + handle text not null, -- nym [@handle@origin] 35 + origin bigint references parsav_servers(id) 36 + on delete cascade, -- null origin = local actor 37 + bio text, 38 + rank smallint not null default 0, 39 + quota integer not null default 1000, 40 + key bytea, -- private if localactor; public if remote 41 + 42 + unique (handle,origin) 43 +); 44 + 45 +drop table if exists parsav_rights cascade; 46 +create table parsav_rights ( 47 + key text, 48 + actor bigint references parsav_actors(id) 49 + on delete cascade, 50 + allow boolean, 51 + 52 + primary key (key,actor) 53 +); 54 + 55 +insert into parsav_actors (handle,rank,quota) values (:'admin',1,0); 56 +insert into parsav_rights (actor,key,allow) 57 + select (select id from parsav_actors where handle=:'admin'), a.column1, a.column2 from (values 58 + ('ban',true), 59 + ('config',true), 60 + ('censor',true), 61 + ('suspend',true), 62 + ('rebrand',true) 63 + ) as a; 64 + 65 +drop table if exists parsav_posts cascade; 66 +create table parsav_posts ( 67 + id bigint primary key default (1+random()*(2^63-1))::bigint, 68 + author bigint references parsav_actors(id) 69 + on delete cascade, 70 + subject text, 71 + body text, 72 + posted timestamp not null, 73 + discovered timestamp not null, 74 + scope smallint not null, 75 + convo bigint, parent bigint, 76 + circles bigint[], mentions bigint[] 77 +); 78 + 79 +drop table if exists parsav_conversations cascade; 80 +create table parsav_conversations ( 81 + id bigint primary key default (1+random()*(2^63-1))::bigint, 82 + uri text not null, 83 + discovered timestamp not null, 84 + head bigint references parsav_posts(id) 85 +); 86 + 87 +drop table if exists parsav_rels cascade; 88 +create table parsav_rels ( 89 + relator bigint references parsav_actors(id) 90 + on delete cascade, -- e.g. follower 91 + relatee bigint references parsav_actors(id) 92 + on delete cascade, -- e.g. follower 93 + kind smallint, -- e.g. follow, block, mute 94 + 95 + primary key (relator, relatee, kind) 96 +); 97 + 98 +drop table if exists parsav_acts cascade; 99 +create table parsav_acts ( 100 + id bigint primary key default (1+random()*(2^63-1))::bigint, 101 + kind text not null, -- like, react, so on 102 + time timestamp not null, 103 + actor bigint references parsav_actors(id) 104 + on delete cascade, 105 + subject bigint -- may be post or act, depending on kind 106 +); 107 + 108 +drop table if exists parsav_log cascade; 109 +create table parsav_log ( 110 + -- accesses are tracked for security & sending delete acts 111 + id bigint primary key default (1+random()*(2^63-1))::bigint, 112 + time timestamp not null, 113 + actor bigint references parsav_actors(id) 114 + on delete cascade, 115 + post bigint not null 116 +); 117 +end;
Added srv.t version [350801ad24].
1 +-- vim: ft=terra 2 +local util = dofile 'common.lua' 3 +local struct srv { 4 + sources: lib.mem.ptr(lib.store.source) 5 + webmgr: lib.net.mg_mgr 6 + webcon: &lib.net.mg_connection 7 +} 8 + 9 +local handle = { 10 + http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque) 11 + switch event do 12 + case lib.net.MG_EV_HTTP_MSG then 13 + lib.dbg('routing HTTP request') 14 + var msg = [&lib.net.mg_http_message](p) 15 + 16 + end 17 + end 18 + end; 19 +} 20 +local char = macro(function(ch) return `[string.byte(ch:asvalue())] end) 21 +local terra cfg(s: &srv, befile: rawstring) 22 + lib.report('configuring backends from ', befile) 23 + 24 + var fr = lib.file.open(befile, [lib.file.mode.read]) 25 + if fr.ok == false then 26 + lib.bail('could not open configuration file ', befile) 27 + end 28 + 29 + var f = fr.val 30 + var c: lib.mem.vec(lib.store.source) c:init(8) 31 + var text: lib.string.acc text:init(64) 32 + do var buf: int8[64] 33 + while true do 34 + var ct = f:read(buf, [buf.type.N]) 35 + if ct == 0 then break end 36 + text:push(buf, ct) 37 + end 38 + end 39 + f:close() 40 + 41 + var cur = text.buf 42 + var segs: tuple(&int8, &int8)[3] = array( 43 + {[&int8](0),[&int8](0)}, 44 + {[&int8](0),[&int8](0)}, 45 + {[&int8](0),[&int8](0)} 46 + ) 47 + var segdup = [terra(s: {rawstring, rawstring}) 48 + var sz = s._1 - s._0 49 + var str = s._0 50 + return [lib.mem.ptr(int8)] { 51 + ptr = lib.str.ndup(str, sz); 52 + ct = sz; 53 + } 54 + end] 55 + var fld = 0 56 + while (cur - text.buf) < text.sz do 57 + if segs[fld]._0 == nil then 58 + if not (@cur == char(' ') or @cur == char('\t') or @cur == char('\n')) then 59 + segs[fld] = {cur, nil} 60 + end 61 + else 62 + if fld < 2 and @cur == char(' ') or @cur == char('\t') then 63 + segs[fld]._1 = cur 64 + fld = fld + 1 65 + segs[fld] = {nil, nil} 66 + elseif @cur == char('\n') or cur == text.buf + (text.sz-1) then 67 + if fld < 2 then lib.bail('incomplete backend line in ', befile) else 68 + segs[fld]._1 = cur 69 + var src = c:new() 70 + src.id = segdup(segs[0]) 71 + src.string = segdup(segs[2]) 72 + src.backend = nil 73 + for i = 0,[lib.store.backends.type.N] do 74 + if lib.str.ncmp(segs[1]._0, lib.store.backends[i].id, segs[1]._1 - segs[1]._0) == 0 then 75 + src.backend = &lib.store.backends[i] 76 + break 77 + end 78 + end 79 + if src.backend == nil then 80 + lib.bail('unknown backend in ', befile) 81 + end 82 + src.handle = nil 83 + fld = 0 84 + segs[0] = {nil, nil} 85 + end 86 + end 87 + end 88 + cur = cur + 1 89 + end 90 + text:free() 91 + 92 + s.sources = c:crush() 93 +end 94 + 95 +--srv.methods.conf_set = terra(self: &srv, key: rawstring, val:rawstring) 96 +-- self.sources.ptr[0]:conf_set(key, val) 97 +--end 98 + 99 +srv.metamethods.__methodmissing = macro(function(meth, self, ...) 100 + local primary, ptr, stat, simple = 0,1,2,3 101 + local tk, rt = primary 102 + local expr = {...} 103 + for _,f in pairs(lib.store.backend.entries) do 104 + local fn = f.field or f[1] 105 + local ft = f.type or f[2] 106 + if fn == meth then 107 + rt = ft.type.returntype 108 + if rt == bool then tk = simple 109 + elseif rt.stat_basetype then tk = stat 110 + elseif rt.ptr_basetype then tk = ptr end 111 + break 112 + end 113 + end 114 + 115 + if tk == primary then 116 + return `self.sources.ptr[0]:[meth]([expr]) 117 + else local ok, empty 118 + local r = symbol(rt) 119 + if tk == ptr then 120 + ok = `r.ptr ~= nil 121 + empty = `[rt]{ptr=nil,ct=0} 122 + elseif tk == stat then 123 + ok = `r.ok ~= false 124 + empty = `[rt]{ok=false,error=1} 125 + elseif tk == simple then 126 + ok = `r == true 127 + empty = `false 128 + end 129 + return quote 130 + var [r] = empty 131 + for i=0,self.sources.ct do var src = self.sources.ptr + i 132 + if src.handle ~= nil then 133 + r = src:[meth]([expr]) 134 + if [ok] then break 135 + else r = empty end 136 + end 137 + end 138 + in r end 139 + end 140 +end) 141 + 142 +srv.methods.start = terra(self: &srv, befile: rawstring) 143 + cfg(self, befile) 144 + var success = false 145 + for i=0,self.sources.ct do var src = self.sources.ptr + i 146 + lib.report('opening data source ', src.id.ptr, '(', src.backend.id, ')') 147 + src.handle = src.backend.open(src) 148 + if src.handle ~= nil then success = true end 149 + end 150 + if not success then 151 + lib.bail('could not connect to any data sources!') 152 + end 153 + 154 + var dbbind = self:conf_get('bind') 155 + var envbind = lib.proc.getenv('parsav_bind') 156 + var bind: rawstring 157 + if envbind ~= nil then 158 + bind = envbind 159 + elseif dbbind.ptr ~= nil then 160 + bind = dbbind.ptr 161 + else bind = '[::]:10917' end 162 + 163 + lib.report('binding to ', bind) 164 + lib.net.mg_mgr_init(&self.webmgr) 165 + self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, nil) 166 + dbbind:free() 167 + 168 +end 169 + 170 +srv.methods.poll = terra(self: &srv) 171 + lib.net.mg_mgr_poll(&self.webmgr,1000) 172 +end 173 + 174 +srv.methods.shutdown = terra(self: &srv) 175 + lib.net.mg_mgr_free(&self.webmgr) 176 + for i=0,self.sources.ct do var src = self.sources.ptr + i 177 + lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')') 178 + src:close() 179 + end 180 + self.sources:free() 181 +end 182 + 183 +return srv
Modified store.t from [a0814e5c61] to [ed1d490f12].
1 1 -- vim: ft=terra 2 -local m = {} 3 - 4 -local backend = { 5 - pgsql = { 2 +local m = { 3 + timepoint = uint64; 4 + scope = lib.enum { 5 + 'public', 'private', 'local'; 6 + 'personal', 'direct', 'circle'; 7 + }; 8 + notiftype = lib.enum { 9 + 'mention', 'like', 'rt', 'react' 10 + }; 11 + relation = lib.enum { 12 + 'follow', 'mute', 'block' 6 13 }; 7 14 } 8 15 9 -struct m.user { 10 - uid: rawstring 11 - nym: rawstring 12 - handle: rawstring 16 +local str = lib.mem.ptr(int8) 17 +str:complete() 18 + 19 +struct m.source 20 + 21 +struct m.rights { 22 + rank: uint16 -- lower = more powerful except 0 = regular user 23 + -- creating staff automatically assigns rank immediately below you 24 + quota: uint32 -- # of allowed tweets per day; 0 = no limit 25 + 26 + -- user powers -- default on 27 + login: bool 28 + visible: bool 29 + post: bool 30 + shout: bool 31 + propagate: bool 32 + upload: bool 33 + 34 + -- admin powers -- default off 35 + ban: bool 36 + config: bool 37 + censor: bool 38 + suspend: bool 39 + rebrand: bool -- modify site's brand identity 40 +} 41 + 42 +terra m.rights_default() 43 + return m.rights { 44 + rank = 0, quota = 1000; 45 + 46 + login = true, visible = true, post = true; 47 + shout = true, propagate = true, upload = true; 48 + 49 + ban = false, config = false, censor = false; 50 + suspend = false, rebrand = false; 51 + } 52 +end 53 + 54 +struct m.actor { 55 + id: uint64 56 + nym: str 57 + handle: str 58 + origin: uint64 59 + bio: str 60 + rights: m.rights 61 + key: str 62 + 63 + source: &m.source 64 +} 65 +terra m.actor:free() 66 + self.nym:free() 67 + self.handle:free() 68 + self.bio:free() 69 + self.key:free() 70 +end 71 + 72 +struct m.range { 73 + time: bool 74 + union { 75 + from_time: m.timepoint 76 + from_idx: uint64 77 + } 78 + union { 79 + to_time: m.timepoint 80 + to_idx: uint64 81 + } 82 +} 83 + 84 +struct m.post { 85 + id: uint64 86 + author: uint64 87 + subject: str 88 + body: str 89 + posted: m.timepoint 90 + discovered: m.timepoint 91 + scope: m.scope.t 92 + mentions: lib.mem.ptr(uint64) 93 + circles: lib.mem.ptr(uint64) --only meaningful if scope is set to circle 94 + convo: uint64 95 + parent: uint64 96 + 97 + source: &m.source 98 +} 99 + 100 +local cnf = terralib.memoize(function(ty,rty) 101 + rty = rty or ty 102 + return struct { 103 + enum: {&opaque, uint64, rawstring} -> intptr 104 + get: {&opaque, uint64, rawstring} -> rty 105 + set: {&opaque, uint64, rawstring, ty} -> {} 106 + reset: {&opaque, uint64, rawstring} -> {} 107 + } 108 +end) 109 + 110 +struct m.notif { 111 + kind: m.notiftype.t 112 + when: uint64 113 + union { 114 + post: uint64 115 + reaction: int8[8] 116 + } 117 +} 118 + 119 +-- backends only handle content on the local server 120 +struct m.backend { id: rawstring 121 + open: &m.source -> &opaque 122 + close: &m.source -> {} 123 + 124 + conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8) 125 + conf_set: {&m.source, rawstring, rawstring} -> {} 126 + conf_reset: {&m.source, rawstring} -> {} 127 + 128 + actor_save: {&m.source, m.actor} -> bool 129 + actor_create: {&m.source, m.actor} -> bool 130 + actor_fetch_xid: {&m.source, rawstring} -> lib.stat(m.actor) 131 + actor_fetch_uid: {&m.source, uint64} -> lib.stat(m.actor) 132 + actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif) 133 + actor_auth: {&m.source, rawstring, rawstring} -> lib.stat(m.actor) 134 + actor_enum: {&m.source} -> lib.mem.ptr(m.actor) 135 + actor_enum_local: {&m.source} -> lib.mem.ptr(m.actor) 136 + 137 + actor_conf_str: cnf(rawstring, lib.mem.ptr(int8)) 138 + actor_conf_int: cnf(intptr, lib.stat(intptr)) 139 + 140 + post_save: {&m.source, &m.post} -> bool 141 + post_create: {&m.source, &m.post} -> bool 142 + actor_post_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post) 143 + convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post) 144 + convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post) 145 + 146 + actor_timeline_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(m.post) 147 + instance_timeline_fetch: {&m.source, m.range} -> lib.mem.ptr(m.post) 148 +} 13 149 14 - localuser: bool 150 +struct m.source { 151 + backend: &m.backend 152 + id: lib.mem.ptr(int8) 153 + handle: &opaque 154 + string: lib.mem.ptr(int8) 15 155 } 156 +terra m.source:free() 157 + self.id:free() 158 + self.string:free() 159 +end 160 +m.source.metamethods.__methodmissing = macro(function(meth, obj, ...) 161 + local q = {...} 162 + -- syntax sugar to forward unrecognized calls onto the backend 163 + return `obj.backend.[meth](&obj, [q]) 164 +end) 165 + 166 +return m