Index: ipc.t ================================================================== --- ipc.t +++ ipc.t @@ -197,24 +197,24 @@ end terra m.emperor:mallack() return lib.mem.heapa(m.ack, self:countpeers()) end terra m.emperor.methods.decree :: { &m.emperor, uint64, rawstring, m.cmd.t, uint64, &m.ack -} -> {} +} -> bool terra m.emperor:decree( tgtclid: uint64, tgtname: rawstring, cmd: m.cmd.t, operand: uint64, result: &m.ack -): {} +): bool if self.client then lib.bail('client attempted to issue IPC decree') end var dem = m.demand { cmd = cmd; operand = operand; empq = self.msqid; -- register to receive replies } var npeers = self:countpeers() - if npeers == 0 then lib.bail('no processes connected to control bus') end + if npeers == 0 then lib.warn('no processes connected to control bus') return false end if tgtclid == 0 and tgtname == nil then lib.dbg('sending to all instances, waiting for edict to become writable') self.edict:sem(0) -- wait for all locks on edict to resolve lib.dbg('locking edict') self.edict:sem(npeers) -- place a read lock for each peer @@ -248,10 +248,11 @@ end end acks:free() if not found then lib.warn('no such instance is currently online and responding to IPC calls') + return false else lib.dbg('located instance, sending command') if mq.msgsnd(tgt, &dem, sizeof(m.demand), 0) == -1 then lib.bail('could not send command to target process') end @@ -266,10 +267,11 @@ break else lib.warn('got spurious response, ignoring') end end end end + return true end terra m.demand:ack(emp: &m.emperor, a: &m.ack) a.clid = emp.clid a.cliq = emp.msqid Index: mem.t ================================================================== --- mem.t +++ mem.t @@ -62,10 +62,11 @@ [recurse and quote self.ptr:free() end or {}] if self.ct > 0 then m.heapf(self.ptr) + self.ptr = nil self.ct = 0 return true end return false end; Index: mgtool.t ================================================================== --- mgtool.t +++ mgtool.t @@ -6,12 +6,13 @@ verbose = {'v', 'increase logging verbosity', inc=1}; quiet = {'q', 'do not print to standard out'}; help = {'h', 'display this list'}; backend_file = {'B', 'init from specified backend file', consume=1}; backend = {'b', 'operate on only the selected backend'}; - instance = {'i', 'specify the instance to control by name', consume=1}; - all = {'A', 'affect all running instances'}; + instance_id = {'i', 'specify the instance to control by name', consume=1}; + instance_serial = {'I', 'specify the instance to control by serial', consume=1}; + all = {'A', 'affect all running instances/backends'}; }, { subcmd = 1 }) local pbasic = lib.cmdparse { help = {'h', 'display this list'} } @@ -19,10 +20,11 @@ } local ctlcmds = { { 'start', 'start a new instance of the server' }; { 'stop', 'stop a running instance' }; + { 'ls', 'list all running instances' }; { 'attach', 'capture log output from a running instance' }; { 'db', 'set up and manage the database' }; { 'user', 'manage users, privileges, and credentials'}; { 'mkroot ', 'establish a new root user with the given handle' }; { 'actor purge-all', 'remove all traces of a user from the database (except local user credentials -- use \27[1mauth all purge\27[m to prevent a user from accessing the instance)' }; @@ -47,13 +49,18 @@ end return str end local struct idelegate { + emperor: &lib.ipc.emperor + all: bool src: &lib.store.source srv: &lib.srv.overlord + + sid: uint64 + iname: rawstring } idelegate.metamethods.__methodmissing = macro(function(meth, self, ...) local expr = {...} local rt for _,f in pairs(lib.store.backend.entries) do @@ -69,10 +76,28 @@ elseif self.src ~= nil then r=self.src:[meth]([expr]) else lib.bail('no data source specified') end in r end end) + +terra idelegate:ipc_send(cmd: lib.ipc.cmd.t, operand: uint64) + var emp = self.emperor + var acks: lib.mem.ptr(lib.ipc.ack) + if self.sid == 0 and self.iname == nil then + if not self.all and emp:countpeers() > 1 then + lib.bail('either specify the instance to control or pass --all to control all instances') + end + acks = emp:mallack() + emp:decree(0,nil, cmd, operand, &acks(0)) -- TODO targeting + else + acks = lib.mem.heapa(lib.ipc.ack, 1) + if not emp:decree(self.sid, self.iname, cmd, operand, &acks(0)) then + acks:free() + end + end + return acks +end local terra gensec(sdest: rawstring) var dest = [&uint8](sdest) lib.crypt.spray(dest,64) for i=0,64 do dest[i] = dest[i] % (0x7e - 0x20) + 0x20 end @@ -94,20 +119,35 @@ lib.dbg('assigning temporary password') dlg:auth_create_pw(uid, reset, pstr { ptr = [rawstring](tmppw), ct = 32 }) end + +local terra ipc_report(acks: lib.mem.ptr(lib.ipc.ack), rep: rawstring) + var decbuf: int8[21] + for i=0,acks.ct do + var num = lib.math.decstr(acks(i).clid, &decbuf[20]) + if acks(i).success then + lib.report('instance #',num,' reports successful ',rep) + else + lib.report('instance #',num,' reports failed ',rep) + end + end +end local emp = lib.ipc.global_emperor local terra entry_mgtool(argc: int, argv: &rawstring): int if argc < 1 then lib.bail('bad invocation!') end lib.noise.init(2) [lib.init] var srv: lib.srv.overlord - var dlg = idelegate { srv = &srv, src = nil } + var dlg = idelegate { + emperor = &emp, srv = &srv; + src = nil, sid = 0, iname = nil, all = false; + } var mode: ctloptions mode:parse(argc,argv) defer mode:free() if mode.version then version() return 0 end if mode.help then @@ -120,11 +160,21 @@ then cnf = @mode.backend_file else cnf = lib.proc.getenv('parsav_backend_file') end if cnf == nil then cnf = [config.prefix_conf .. "/backend.conf"] end if mode.all then dlg.all = true else - -- iterate through and pick the right backend + -- iterate through and pick the right backend, if one is indicated + end + + if mode.instance_id ~= nil and mode.instance_serial ~= nil then + lib.bail('conflicting flags passed') + end + + if mode.instance_id ~= nil then + dlg.iname = @mode.instance_id + elseif mode.instance_serial ~= nil then + -- decode serial end if mode.arglist.ct == 0 then lib.bail('no command') return 1 end if lib.str.cmp(mode.arglist(0),'start') ~= 0 then @@ -133,22 +183,40 @@ end defer emp:release() if lib.str.cmp(mode.arglist(0),'attach') == 0 then elseif lib.str.cmp(mode.arglist(0),'start') == 0 then - mode.arglist(0) = "-"; - var chargv = mode.arglist.ptr + var smode: lib.cmdparse { + help = {'h', 'display this list'}; + log = {'l', 'send server\'s logging output to a file', consume=1}; + pid = {'p', 'report PID of launched process'}; + instance_serial = {'s', 'report the instance serial of the launched process'}; + keep_attached = {'k', 'don\'t detach from the calling terminal until a successful startup (or ever, if passed twice)', inc=1}; + } + smode:parse(mode.arglist.ct,mode.arglist.ptr) + if smode.help then + [ lib.emit(false, 1, 'usage: ', `argv[0], ' start ', smode.type.helptxt.flags, ' […] [-- …]', smode.type.helptxt.opts) ] + return 1 + end var lsr = lib.ipc.listener.mk() var chpid = lib.proc.fork() if chpid == 0 then lsr:release() - --lib.proc.daemonize(1,0) - lib.io.close(0) lib.io.close(1) lib.io.close(2) - lib.proc.exec([config.prefix_bin .. '/parsavd'], chargv) - lib.proc.execp([config.prefix_bin .. '/parsavd'], chargv) + var chargv = lib.mem.heapa(rawstring, smode.arglist.ct + 2) + chargv(0) = '-' + for i = 1, chargv.ct - 1 do + chargv(i) = smode.arglist(i-1) + end + chargv(chargv.ct-1) = nil + if smode.keep_attached == 0 then + lib.io.close(0) lib.io.close(1) lib.io.close(2) + end + lib.proc.exec([config.prefix_bin .. '/parsavd'], chargv.ptr) + lib.proc.execp([config.prefix_bin .. '/parsavd'], chargv.ptr) lib.ipc.notify_parent(lib.ipc.signals.state_fail_find) lib.bail('cannot find parsav program') + -- chargv:free() else lib.report('starting parsav daemon') while true do var sig = lsr:block() if sig.system then lib.dbg('got system signal') end @@ -168,20 +236,28 @@ end end lsr:release() -- just because i feel distinctly uncomfortable leaving it out end elseif lib.str.cmp(mode.arglist(0),'stop') == 0 then - var acks = emp:mallack() - emp:decree(0,nil, lib.ipc.cmd.stop, 0, &acks(0)) -- TODO targeting - for i=0,acks.ct do - if acks(i).success then - lib.io.fmt('instance %llu successfully stepped down\n', acks(i).clid) - else - lib.io.fmt('instance %llu reports failure to halt\n', acks(i).clid) - end + if mode.arglist.ct ~= 1 then goto cmderr end + var acks = dlg:ipc_send(lib.ipc.cmd.stop, 0) + if acks:ref() then + ipc_report(acks, 'step-down') + acks:free() end - acks:free() + elseif lib.str.cmp(mode.arglist(0),'ls') == 0 then + if mode.arglist.ct ~= 1 then goto cmderr end + if dlg.sid == 0 and dlg.iname == nil then dlg.all = true end + var acks = dlg:ipc_send(lib.ipc.cmd.enumerate, 0) + if acks:ref() then + for i=0,acks.ct do + var decbuf: int8[21] + var num = lib.math.decstr(acks(i).clid, &decbuf[20]) + [ lib.emit(true,1, '\27[1m(', `num, ')\27[m ',`&acks(i).iname[0],' \27[1;32mactive\27[m') ] + end + acks:free() + else lib.bail('no active instances') end else if lib.str.cmp(mode.arglist(0),'db') == 0 then var dbmode: pbasic dbmode:parse(mode.arglist.ct, &mode.arglist(0)) if dbmode.help then [ lib.emit(false, 1, 'usage: ', `argv[0], ' db ', dbmode.type.helptxt.flags, ' […]', dbmode.type.helptxt.opts, cmdhelp { Index: parsav.t ================================================================== --- parsav.t +++ parsav.t @@ -431,10 +431,12 @@ backend_file = {'B', 'init from specified backend file', consume=1}; static_dir = {'S', 'directory with overrides for static content', consume=1}; builtin_data = {'D', 'do not load static content overrides at runtime under any circumstances'}; instance = {'i', 'set an instance name to make it easier to control multiple daemons', consume = 1}; no_ipc = {'I', 'disable IPC'}; + chroot = {'C', 'chroot to the specified directory after starting and connecting to backends', consume=1}; + no_lockdown = {'L', 'don\'t drop privileges or apply other security measures that could cause compatibility or communication problems'} } local static_setup = quote end local mapin = quote end Index: srv.t ================================================================== --- srv.t +++ srv.t @@ -621,11 +621,11 @@ if dbbind.ptr ~= nil then dbbind:free() end end terra srv:poll() - lib.net.mg_mgr_poll(&self.webmgr,1000) + lib.net.mg_mgr_poll(&self.webmgr,300) end terra srv:shutdown() lib.net.mg_mgr_free(&self.webmgr) for i=0,self.sources.ct do var src = self.sources.ptr + i