-- -- gcsd -- -- gcsd the generic command sequencer daemon can be used to serialize -- commands sent over various paralell communication channels to a -- single command output. It can be seen as a multiplexer for any -- kind of communication between a single resource and various clients -- which want to submit commands to it or query information from it. -- gcsd is written in C and Lua. The goal is to provide an easy to -- understand high level API based on Lua which can be used to -- implement the business logic of the so formed multiplexer daemon. -- -- -- Copyright (C) 2009-2010 Markus Grueneis -- Christian Pointner -- -- This file is part of gcsd. -- -- gcsd is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- any later version. -- -- gcsd is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with gcsd. If not, see . -- local socket = require("socket") -- debug shell module class local debug_shell = {} debug_shell.properties = { type=defines.MISC_MODULE, name="debug_shell", max_instances=-1 } debug_shell.defaults = { host = "localhost", port = 9000 } debug_shell.next_id = 0 -- create new instance of debug shell module class function debug_shell:new(config, runtype) local inst = {} inst.class = self.properties.name inst.config = config if(config.name == nil or config.name == "") then inst.name = self.properties.name .. self.next_id self.next_id = self.next_id + 1 else inst.name = config.name end if(not config.host) then config.host = self.defaults.host end if(not config.port) then config.port = self.defaults.port end local ip, err = socket.dns.toip(config.host) if(ip == nil) then log.printf(log.ERROR, inst.name .. ": can't resolve %s: %s", config.host, err) return nil end local server_sock_raw, err = socket.tcp() if(server_sock_raw == nil) then log.printf(log.ERROR, inst.name .. ": can't create tcp socket") return nil end server_sock_raw:setoption('reuseaddr', true); local ret, err = server_sock_raw:bind(ip, config.port) if(ret == nil) then log.printf(log.ERROR, inst.name .. ": bind(%s,%s) failed: %s", ip, config.port, err) return nil end local ret, err = server_sock_raw:listen() if(ret == nil) then log.printf(log.ERROR, inst.name .. ": listen() failed: %s", err) return nil end server_sock_raw:settimeout(0) local server_sock = {} server_sock.sock = server_sock_raw function server_sock:getfd() return self.sock:getfd() end function server_sock:read() local client_sock_raw, err = self.sock:accept() if(client_sock_raw == nil) then log.printf(log.ERROR, inst.name .. ": accept() failed: %s", err) end local ip, port = client_sock_raw:getpeername(); log.printf(log.INFO, inst.name .. ": connection from %s:%s accepted", ip, port) client_sock_raw:settimeout(0); client_sock_raw:setoption('tcp-nodelay', true); local client_sock = {} client_sock.sock = client_sock_raw function client_sock:getfd() return self.sock:getfd() end client_sock.client_instance = nil client_sock.in_buffer = "" client_sock.out_buffer = "" function client_sock:read() local ret = defines.OK local data, err, partial = self.sock:receive('*l') if(data == nil) then if(err == 'closed') then log.printf(log.INFO, inst.name .. ": connection closed by peer") ret = defines.KILL_CLIENT elseif(err == 'timeout') then self.in_buffer = self.in_buffer .. partial else log.printf(log.INFO, inst.name .. ": connection error: %s", err) ret = defines.KILL_CLIENT end else self.in_buffer = self.in_buffer .. data ret = debug_shell:exec_cmd(self) self.in_buffer = "" end return ret end function client_sock:write() local ret = defines.OK local len, err, partiallen = self.sock:send(self.out_buffer) if(len == nil) then if(err == 'closed') then log.printf(log.INFO, inst.name .. ": connection closed by peer") ret = defines.KILL_CLIENT elseif(err == 'timeout') then self.out_buffer = string.sub(self.out_buffer, partiallen+1) else log.printf(log.INFO, inst.name .. ": connection error: %s", err) ret = defines.KILL_CLIENT end else self.out_buffer = string.sub(self.out_buffer, len+1) end return ret end local client = {} client.module_instance = inst client.name = inst.name .. "#" .. ip .. ":" .. port client.sock = client_sock function client:process_response(response) self.sock.out_buffer = self.sock.out_buffer .. response end function client:process_timeout() end function client:get_read_handles() return { self.sock } end function client:get_write_handles() if(self.sock.out_buffer ~= "") then return { self.sock } else return {} end end function client:cleanup() self.sock.sock:close() end client_sock.client_instance = client client_list:register(client) return defines.OK end function server_sock:write() return defines.OK end log.printf(log.WARNING, inst.name .. ": listening on %s:%s (this is a huge security risk)", ip, config.port); function inst:cleanup() client_list:unregister_by_module(self) server_sock.sock:close() end function inst:get_read_handles() return { server_sock } end function inst:get_write_handles() return {} end setmetatable(inst, {}) getmetatable(inst).__gc = function() inst:cleanup() end return inst end function debug_shell:exec_cmd(socket) local client_name = socket.client_instance.name log.printf(log.DEBUG, client_name .. ": received string: '%s'", socket.in_buffer) local ret = defines.OK local luacode = string.match(socket.in_buffer, "^!(.*)$"); if(luacode and luacode ~= "") then local chunk = loadstring(luacode) if(chunk) then log.printf(log.DEBUG, client_name .. ": calling lua command: '%s'", luacode) local results = { pcall(chunk) } if(results[1]) then for i,result in ipairs(results) do if(i > 1) then socket.out_buffer = socket.out_buffer .. string.format("#%d: %s\n", i-1, tostring(result)) end end else socket.out_buffer = socket.out_buffer .. string.format("lua call failed: %s\n", tostring(results[2])) end else socket.out_buffer = socket.out_buffer .. "syntax error\n" end else local cmd, sep, param = string.match(socket.in_buffer, "^(%w+)(.?)(.*)$"); if(cmd == 'quit') then if(sep and sep ~= "") then socket.out_buffer = socket.out_buffer .. "unknown command\n" end log.printf(log.INFO, client_name .. ": quitting debug session") ret = defines.KILL_CLIENT elseif(cmd == 'disable') then if(sep and sep ~= "") then socket.out_buffer = socket.out_buffer .. "unknown command\n" end log.printf(log.NOTICE, client_name .. ": disabling this debug shell instance and close all connections") ret = defines.KILL_MODULE elseif(cmd == 'disableall') then if(sep and sep ~= "") then socket.out_buffer = socket.out_buffer .. "unknown command\n" end log.printf(log.NOTICE, client_name .. ": disabling all debug shell instances and close all connections") ret = defines.KILL_MODULE_CLASS elseif(cmd == 'terminate') then if(sep and sep ~= "") then socket.out_buffer = socket.out_buffer .. "unknown command\n" end log.printf(log.NOTICE, client_name .. ": terminate command received") ret = defines.KILL_DAEMON elseif(cmd == 'help') then if(sep and sep ~= "") then socket.out_buffer = socket.out_buffer .. "unknown command\n" end socket.out_buffer = socket.out_buffer .. "! execute lua code\n" .. "cmd add the command to the command queue\n" .. "quit quit this debug session\n" .. "ping[ ] echo request (response will be 'pong[ ]')\n" .. "disable close this debug shell instance and close all connections\n" .. "disableall close all debug shell instances and close all connections\n" .. "terminate terminate the daemon\n" elseif(cmd == 'ping') then if(sep == ' ') then socket.out_buffer = socket.out_buffer .. "pong " .. (param or "") .. "\n" else socket.out_buffer = socket.out_buffer .. "pong\n" end elseif(cmd == 'cmd') then if(not param or param == '') then socket.out_buffer = socket.out_buffer .. "Error: please specify a command\n" else socket.out_buffer = socket.out_buffer .. "enqueing command: " .. param .. "\n" command_queue:enqueue(param, "ok\n", 10000) end else socket.out_buffer = socket.out_buffer .. "unknown command\n" end end return ret end return debug_shell