--------------------------------------------------------------------------------
--
-- Módulo SGA Unix
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Chaves padrões e chaves disponíveis da biblioteca
--------------------------------------------------------------------------------

-- Chaves padrões de configuração da biblioteca
_libDefaltConfigKeys = {
  "csbase_num_processors",
  "csbase_memory_ram_info_mb",
  "csbase_memory_swap_info_mb",
  "csbase_job_control_actions",
}

-- Chaves padrões de configuração definidas pelo usuário
_userDefaltConfigKeys = _libDefaltConfigKeys

-- Chaves de configuração disponíveis
-- O valor true indica que é um chave obrigatório e false indica que é opcional
_allConfigKeys = {
  csbase_num_processors = true,
  csbase_memory_ram_info_mb = true,
  csbase_memory_swap_info_mb = true,
  csbase_job_control_actions = true,
}

-- Chaves padrões de informações dinâmicas da biblioteca
_libDefaltInfoKeys = {
  "csbase_load_perc",
  "csbase_memory_ram_free",
  "csbase_memory_swap_free",
  "csbase_number_of_jobs",
}

-- Chaves padrões de informações dinâmicas definidas pelo usuário
_userDefaltInfoKeys = _libDefaltInfoKeys

-- Chaves de informações dinâmicas disponíveis
-- O valor true indica que é um chave obrigatório e false indica que é opcional
_allInfoKeys = {
  csbase_load_perc = true,
  csbase_memory_ram_free = true,
  csbase_memory_swap_free = true,
  csbase_number_of_jobs = true,
}

-- Chaves padrões de informações de processo da biblioteca
_libDefaltProcessKeys = {
  "csbase_command_pid",
  "csbase_command_string",
  "csbase_command_exec_host",
  "csbase_command_state",
  "csbase_command_memory_ram_size_mb",
  "csbase_command_memory_swap_size_mb",
  "csbase_command_cpu_perc",
  "csbase_command_time_sec",
  "csbase_command_wall_time_sec",
  "csbase_command_user_time_sec",
  "csbase_command_system_time_sec",
}

-- Chaves de informações de processos definidas pelo usuário
_userDefaltProcessKeys = _libDefaltProcessKeys

-- Chaves de informações de processos disponíveis
-- O valor true indica que é um chave obrigatório e false indica que é opcional
_allProcessKeys = {
  csbase_command_pid = true,
  csbase_command_string = true,
  csbase_command_exec_host = true,
  csbase_command_state = true,
  csbase_command_memory_ram_size_mb = true,
  csbase_command_memory_swap_size_mb = true,
  csbase_command_cpu_perc = true,
  csbase_command_time_sec = true,
  csbase_command_wall_time_sec = true,
  csbase_command_user_time_sec = true,
  csbase_command_system_time_sec = true,
}

--------------------------------------------------------------------------------
--
-- Especificação das funções públicas
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Incializa o módulo para execução. Essa função deve ser chamada antes
-- de qualquer outra função desta biblioteca.
--
-- Retorna verdadeiro caso o módulo seja inicializado com sucesso. Em caso
-- de erro, retorna nil e a mensagem de erro.
--
-- ok, err = open()
--------------------------------------------------------------------------------
open = function()
  local platform = os.getenv("TEC_UNAME")
  if not platform then
    platform = os.getenv("TEC_SYSNAME")
  end
  PATTERNS:init(platform)
  PROCESSES:init()

  return true
end -- function open

--------------------------------------------------------------------------------
-- Termina a execução do módulo. Os processos disparados que ainda estejam
-- rodando não são afetados por essa chamada. Essa função não retorna valores.
--
-- close()
--------------------------------------------------------------------------------
close = function()
  servermanager._nodes = nil
end -- function close

--------------------------------------------------------------------------------
--
-- Funções de consulta à configuração de servidor.
--
-- Em SGAs que são clusters, o parâmetro server indica o nó sobre o
-- qual se deseja obter a informação.
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
setDefaultKeys = function(keys)
  _userDefaltConfigKeys = {}
  _userDefaltInfoKeys = {}
  _userDefaltProcessKeys = {}
  for _,v in ipairs(keys) do
    -- Config keys
    if(_allConfigKeys[v]) then
      table.insert(_userDefaltConfigKeys, v)
    end -- Config keys
    -- Info keys
    if(_allInfoKeys[v]) then
      table.insert(_userDefaltInfoKeys, v)
    end -- Info keys
    -- Process keys
    if(_allProcessKeys[v]) then
      table.insert(_userDefaltProcessKeys, v)
    end -- Process keys
  end
end -- function setDefaultKeys

--------------------------------------------------------------------------------
--
--------------------------------------------------------------------------------
getAllKeys = function()
  local allKeys = {}
  for k,_ in pairs(_allConfigKeys) do
    table.insert(allKeys, k)
  end

  for k,_ in pairs(_allInfoKeys) do
    table.insert(allKeys, k)
  end

  for k,_ in pairs(_allProcessKeys) do
    table.insert(allKeys, k)
  end
end -- function getAllKeys

--------------------------------------------------------------------------------
-- Retorna as informações de configuração correspondentes às chaves e ao
-- servidor especificados.
--
-- @param server o servidor do qual as informações serão obtidas. Caso o valor
--        seja nil será assumido localhost.
-- @param keys a lista contendo as chaves das informações que serão obtidas. Se
--        a lista for nil ou vazia serão retornadas as informações padrões.
--
-- @return o mapa contendo as chaves com o valor correspondente da informação e
--         uma mensagem em caso de erro.
--         As informações padrões que devem ser suportadas são:
--           csbase_num_processors
--           csbase_memory_ram_info_mb
--           csbase_memory_swap_info_mb
--           csbase_job_control_actions
--         Além destas, podem-se adicionar informações padrões específicas de
--         plataforma.
--
-- map, err = getConfiguration(server, keys)
--------------------------------------------------------------------------------
getConfiguration = function(server, keys)
  if not keys or #keys == 0 then
    keys = _userDefaltConfigKeys
  end -- if no keys

  local errorKeys = {}

  local configMap = {}
  for _, k in ipairs(keys) do
    if k == "csbase_num_processors" then
      local numCPU, err = _getNumCPU(server)
      if err then
        table.insert(errorKeys, "csbase_num_processors")
      else
        configMap[k] = numCPU
      end
    elseif k == "csbase_memory_ram_info_mb" then
      local memory, err = _getMemory(server)
      if err then
        table.insert(errorKeys, "csbase_memory_ram_info_mb")
      else
        configMap[k] = memory.ram
      end
    elseif k == "csbase_memory_swap_info_mb" then
      local memory, err = _getMemory(server)
      if err then
        table.insert(errorKeys, "csbase_memory_swap_info_mb")
      else
        configMap[k] = memory.swap
      end
    elseif k == "csbase_job_control_actions" then
      configMap[k] = _getCommandActions()
    end
  end

  local errorMsg = _buildErrMsg(errorKeys)

  return configMap, errorMsg
end -- function getConfiguration

--------------------------------------------------------------------------------
--
-- Funções de monitoração de servidor.
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Retorna as informações dinâmicas correspondentes às chaves e ao servidor
-- especificados.
--
-- @param server o servidor do qual as informações serão obtidas. Caso o valor
--        seja nil será assumido localhost.
-- @param keys a lista contendo as chaves das informações que serão obtidas. Se
--        a lista for nil ou vazia serão retornadas as informações padrões.
-- @param maxold indica o tempo máximo que a informação pode ter. Caso maxold
--        seja nil, é assumido zero e a informação será buscada.
--
-- @return o mapa contendo as chaves com o valor correspondente da informação e
--         uma mensagem em caso de erro.
--         As informações padrões são:
--           csbase_load_perc
--           csbase_memory_ram_free
--           csbase_memory_swap_free
--           csbase_number_of_jobs
--         Além destas, podem-se adicionar informações padrões específicas de
--         plataforma.
--
-- Algunes detalhes sobre as informações padrões:
-- csbase_load_perc:
--   a taxa média de ocupação de CPU do último minuto. Esse valor considera o
--   número de processadores que o servidor possui. Por exemplo, caso a métrica
--   de ocupação seja o número de processos na fila de prontos, este número
--   estará dividido pela quantidade de processadores.
-- csbase_memory_ram_free_perc e csbase_memory_swap_free_perc:
--   a média de ocupação, em bytes, de memória do último minuto.
-- csbase_number_of_jobs:
--   o número de jobs que estão em execução.
--
-- map, err = getInfo(server, keys, maxold)
--------------------------------------------------------------------------------
getInfo = function(server, keys, maxold)
  if not keys or #keys == 0 then
    keys = _userDefaltInfoKeys
  end -- if no keys

  local errorKeys = {}
  local infoMap = {}
  for _, k in ipairs(keys) do
    if k == "csbase_load_perc" then
      local load, err = _getCPULoad(server, maxold)
      if not err then
        infoMap[k] = load
      else
        table.insert(errorKeys, k)
      end
    elseif k == "csbase_memory_ram_free" then
      local load, err = _getMemoryLoad(server, maxold)
      if not err then
        infoMap[k] = load.ram
      else
        table.insert(errorKeys, k)
      end
    elseif k == "csbase_memory_swap_free" then
      local load, err = _getMemoryLoad(server, maxold)
      if not err then
        infoMap[k] = load.swap
      else
        table.insert(errorKeys, k)
      end
    elseif k == "csbase_number_of_jobs" then
      local nJobs, err = _getNumberOfJobs(server, maxold)
      if not err then
        infoMap[k] = nJobs
      else
        table.insert(errorKeys, k)
      end
    end
  end

  local errorMsg = _buildErrMsg(errorKeys)
  return infoMap, errorMsg
end -- function getInfo

--------------------------------------------------------------------------------
--
-- Funções de execução, monitoração e controle de processos.
--
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
-- Executa um comando no servidor especificado e retorna um handle que é a
-- referência para o comando no contexto desta biblioteca. O handle pode ter
-- qualquer formato.
-- Os parâmetros extras para a execução podem ser específicos da plataforma ou
-- um dos seguintes já definidos:
--   csbase_command_input_path caminho do direcionamento da entrada padrão.
--   csbase_command_output_path caminho do direcionamento da saída padrão.
--   csbase_command_error_path caminho do direcionamento da saída de erro.
-- Os valores de csbase_command_output_path e csbase_command_error_path podem
-- apontar para o mesmo caminho.
--
-- @param id o identificador único para este comando.
-- @param command o comando a ser executado.
-- @param extraParams os parâmetros extras para a excução do comando.
-- @param server o servidor onde o comando será executado. Caso seja nil será
--        assumido localhost.
--
-- @return o handle do comando e, em caso de erro, nil e a mensagem de erro.
--
-- handle, err = executeCommand(id, command, extraParams, requirements, server)
--------------------------------------------------------------------------------
-- executeCommand = function(id, command, infile, outfile, errorfile, server)
executeCommand = function(id, command, extraParams, server)
-- const string COMMAND_EXECUTION_INPUT_PATH = "csbase_command_input_path";
-- const string COMMAND_EXECUTION_OUTPUT_PATH = "csbase_command_output_path";
-- const string COMMAND_EXECUTION_ERROR_PATH = "csbase_command_error_path";
   local handle = { command = command }
   -- local host = server or servermanager.name
   local host = server
   local execute_function = "execute_command"
   -- if host ~= servermanager.name then
   --    execute_function = "execute_remote_command"
   -- end

   handle.host = host
   handle.time_file = os.tmpname()

   -- Recupera a função de execução apropriada que monta a string
   -- de execução do comando.
   local exec_function = PATTERNS[execute_function]
   if type(exec_function) ~= type(function() end) then
      local errmsg =  "ERRO DE CONFIGURAÇÃO DE [execute_command]."
      servermanager.writeError(errmsg)
      return nil, errmsg
   end

   -- Monta a string de execução
   local exec_string = exec_function( handle ) or "true"
   servermanager.writeCmd( "Tentando execução:\n\t"..exec_string.."\n" )

   -- Executa o comando e guarda o PID
   handle.last_update = 0
   handle.cmdid = id
   handle.pid = servermanager._create_process{
      cmd = exec_string,
      input = extraParams["csbase_command_input_path"] or "/dev/null",
      output = extraParams["csbase_command_output_path"] or "/dev/null",
      err_output = extraParams["csbase_command_error_path"] or "/dev/null",
   }

   return handle, nil
end -- function executeCommand

--------------------------------------------------------------------------------
-- Excerce uma ação sobre um comando iniciado através da função executecommand.
--
-- @param handle o handle do comando.
-- @param action a ação que será feito sobre o comando. Os valores estão
--        definidos na enumeração JobControlAction:
--          SUSPEND: suspender a execução do comando;
--          RESUME: (re)iniciar o comando;
--          HOLD: alterar o estado do comando para on-hold;
--          RELEASE: retirar o comando do estado de on-hold;
--          TERMINATE: terminar o comando.
--
-- @return true se a ação foi exercida com sucesso e, se cocorrer algum erro,
--         false ou nil e a mensagem de erro.
--
-- ok, ex = control(handle, action)
--------------------------------------------------------------------------------
control = function(handle, action, childId)
-- @TODO Pegar o enumerador da IDL para validar
  if action ~= "TERMINATE" then
    return nil, "Ação não suportada"
  end

  if type(handle) ~= "table" or
    not handle.pid or not tonumber(handle.pid) then
    return nil, "Handle inválido."
  end

  local idx = PROCESSES:getIndex(handle.pid)
  if not idx then
    return nil, "Comando não encontrado."
  end
  local idx_children = PROCESSES:getIndexesFromChildren(idx)

  kill_process( handle.pid )
  for i,idx in ipairs(idx_children) do
    local cpid = PROCESSES:getProcessId(idx)
    if cpid then
      kill_process(cpid)
    end
  end

  return 1
end -- function control

--------------------------------------------------------------------------------
-- Retorna uma tabela Lua com os dados do comando que precisam ser persistidos.
-- Estes dados devem ser suficientes para que um comando seja 'recuperado' após
-- a reinicialização do SGA, através da chamada à retrievecommandhandle.
--
-- @param handle o handle do comando.
--
-- @return os dados do comando ou nil e mensagem de erro na ocorrência de erro.
--
-- pdata, err = getcommandpersistentdata(handle)
--------------------------------------------------------------------------------
getCommandPersistentData = function(handle)
  if type(handle) ~= "table" or not handle.pid
     or not tonumber(handle.pid) then
     return nil, "Handle inválido."
  end

  if (not handle.pscommand) then
     PROCESSES:readProcessesInformation()
     local idx = PROCESSES:getIndex(handle.pid)
     if not idx then
        return nil, "Comando não encontrado [" .. handle.cmdid .. "]."
     end

     handle.pscommand = PROCESSES:getCommandString(idx)
  end
  return { pid = tonumber(handle.pid),
           cmdid = handle.cmdid,
           time_file = handle.time_file,
           pscommand = handle.pscommand,
           host = handle.host,
         }, nil
end -- function getCommandPersistentData

--------------------------------------------------------------------------------
-- A partir dos dados persistidos, retorna um handle que é a referência para o
-- comando no contexto desta biblioteca. O handle pode ter qualquer formato.
--
-- @param pdata os dados do comando.
--
-- @return o handle do comando ou nil e mensagem de erro na ocorrência de erro.
--
-- handle, err = retrievecommandhandle(pdata)
--------------------------------------------------------------------------------
retrieveCommandHandle = function(pdata)
   if type(pdata) ~= "table" or not pdata.pid or
      not tonumber(pdata.pid) then
      return nil, "Dados inválidos."
   end

   PROCESSES:readProcessesInformation()
   local idx = PROCESSES:getIndex(pdata.pid)
   if idx then -- Se o processo ainda está em execução, devemos verificar
               -- se este é o processo que haviamos disparado.
     local command = PROCESSES:getCommandString(idx)

     if command and command ~= pdata.pscommand then
        -- Nao posso dizer que recuperou porque existe outro processo
        -- com o mesmo pid. Apaga arquivo de tempos, mas no futuro, pode
        -- fazer retornar a notificacao e os tempos coletados...
        os.remove(pdata.time_file)
        return nil, "Comando não encontrado."
     end
   end

   pdata.last_update = 0
   return pdata, nil
end -- function retrieveCommandHandle

--------------------------------------------------------------------------------
-- Retorna as informações correspondentes às chaves e ao comando especificados.
--
-- @param handle o handle do comando.
-- @param keys a lista contendo as chaves das informações que serão obtidas. Se
--        a lista for nil ou vazia serão retornadas as informações padrões.
-- @param maxold indica o tempo máximo que a informação pode ter. Caso maxold
--        seja nil, é assumido zero e a informação será buscada.
--
-- @return o mapa contendo as chaves com o valor correspondente da informação e
--         uma mensagem em caso de erro.
--         As informações padrões são:
--           csbase_command_pid
--           csbase_command_string
--           csbase_command_exec_host
--           csbase_command_state
--           csbase_command_memory_ram_size_mb
--           csbase_command_memory_swap_size_mb
--           csbase_command_cpu_perc
--           csbase_command_time_sec
--           csbase_command_wall_time_sec
--           csbase_command_user_time_sec
--           csbase_command_system_time_sec
--         Além destas, podem-se adicionar informações padrões específicas de
--         plataforma.
--
-- Algunes detalhes sobre as informações padrões:
-- csbase_command_id:
--   identificador do comando recebido na função executecommand.
-- csbase_command_pid:
--   identificador do processo iniciado através da função executecommand.
-- csbase_command_ppid:
--   identificador do processo pai iniciado através da função executecommand.
-- csbase_command_string:
--   o comando em execução.
-- csbase_command_exec_host:
--   a máquina que está executando o comando.
-- csbase_command_state:
--   o estado de um processo iniciado através da função executecommand.
-- csbase_command_memory_ram_size_mb:
--   a média de ocupação de memória do último minuto (em MB).
-- csbase_command_memory_swap_size_mb:
--   a média de ocupação da área de swap do último minuto (em MB).
-- csbase_command_cpu_perc:
--   a taxa média de ocupação de CPU do último minuto pelo comando.
-- csbase_command_cpu_time_sec:
--   o tempo de CPU da execução do comando (em SEC).
-- csbase_command_wall_time_sec:
--   o tempo de relógio da execução do comando (em SEC).
-- csbase_command_user_time_sec:
--   o tempo de usuário da execução do comando (em SEC).
-- csbase_command_system_time_sec:
--   o tempo de systema da execução do comando (em SEC).
--
-- map, err = getCommandInfo(handle, keys, maxold)
--------------------------------------------------------------------------------
getCommandInfo = function(handle, keys, maxold)
  if type(handle) ~= "table" or not handle.cmdid then
    return nil, "Handle inválido."
  end

  local time, err = _getCommandTimeUsage(handle, maxold)
  if err then
    time = {}
    time.elapsed = 0
    time.user = 0
    time.system = 0
  end

  local mem, err = _getCommandMemoryLoad(handle, maxold)
  if err then
    mem = {}
    mem.ram = 0
    mem.swap = 0
  end

  if not keys or #keys == 0 then
    keys = _userDefaltProcessKeys
  end -- if no keys

  local cmdInfoMap = {}

  for _, k in ipairs(keys) do
    if k == "csbase_command_id" then
      cmdInfoMap[k] = _getCommandId(handle)
    elseif k == "csbase_command_pid" then
      cmdInfoMap[k] = _getCommandPid(handle, maxold)
    elseif k == "csbase_command_ppid" then
      cmdInfoMap[k] = 0
    elseif k == "csbase_command_string" then
      cmdInfoMap[k] = handle.command
    elseif k == "csbase_command_exec_host" then
      cmdInfoMap[k] = _getCommandExecHost(handle, maxold)
    elseif k == "csbase_command_state" then
      cmdInfoMap[k] = _getCommandStatus(handle, maxold)
    elseif k == "csbase_command_memory_ram_size_mb" then
      cmdInfoMap[k] = mem.ram
    elseif k == "csbase_command_memory_swap_size_mb" then
      cmdInfoMap[k] = mem.swap
    elseif k == "csbase_command_cpu_perc" then
      cmdInfoMap[k] = _getCommandCPULoad(handle, maxold)
    elseif k == "csbase_command_cpu_time_sec" then
      cmdInfoMap[k] = 0
    elseif k == "csbase_command_wall_time_sec" then
      cmdInfoMap[k] = time.elapsed
    elseif k == "csbase_command_user_time_sec" then
      cmdInfoMap[k] = time.user
    elseif k == "csbase_command_system_time_sec" then
      cmdInfoMap[k] = time.system
    end
  end

  return cmdInfoMap, nil
end -- function getCommandInfo

------------------------------------------------------------------------------
-- Obtém as informações dos jobs em execução no SGA
------------------------------------------------------------------------------
getJobsInfo = function()
   return nil
end -- function getJobsInfo

------------------------------------------------------------------------------
-- Inicializa a variável que indica se o histórico deve ser ativado ou não
-- para o sga em questão.
------------------------------------------------------------------------------
setHistoric = function (historic)
  enableHistoric = historic
end -- function setHistoric

------------------------------------------------------------------------------
-- Retorna variável que indica se o histórico deve ser ativado ou não para o
-- sga em questão.
--
-- enableHistoric = getHistoric()
------------------------------------------------------------------------------
getHistoric = function ()
  return enableHistoric
end -- function getHistoric

------------------------------------------------------------------------------
-- Libera todos os recursos relacionados ao processo especificado. Esta função
-- precisa ser chamada após o término do processo para que eventuais recursos
-- alocados a ele possam ser liberados. Após a chamada desta função, o handle
-- do processo não poderá mais ser utilizado.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- ok, err = releaseCommandResources(handle)
------------------------------------------------------------------------------
releaseCommandResources = function (handle)
   os.remove(handle.time_file)
   return 1
end -- function releaseCommandResources

--
-- Funções de controle do módulo.
--

--------------------------------------------------------------------------------
--
-- Cria um novo processo e retorna o PID
--
--------------------------------------------------------------------------------
function _create_process(p)
  if type(p) == 'string' then
    p = {cmd = p}
  end

  if not p.argv then
    local function proc_argv(x)
      x = string.gsub(x, [[([\'\"])(.*)%1]],
        function(_, s)
          return string.gsub(s, '%s', '\\1')
        end)
      local t = {}
      string.gsub(x, '(%S+)',
        function(s)
          s = string.gsub(s, '\\1', ' ')
          table.insert(t, s)
        end)
      return t
    end -- function proc_argv

    p.argv = proc_argv(p.cmd)
    if type(p.argv) == 'table' then
      p.cmd = p.argv[1]
    end
  elseif type(p.argv) == 'table' and not p.cmd then
    p.cmd = p.argv[1]
  end

  return unix_create_process(p)
end -- function _create_process

------------------------------------------------------------------------------
--
-- Especificação das funções privadas
--
------------------------------------------------------------------------------
require("lib.Unix_patterns")
require("lib.Unix_processes")

--------------------------------------------------------------------------------
--
-- Obtém o nó
--
--------------------------------------------------------------------------------
_getnode = function(server)
   -- server = server or servermanager.name
   local ninfo = servermanager._nodes[server]
   if not ninfo then
      ninfo = servermanager._add(server)
   end
   return ninfo
end -- function _getnode

--------------------------------------------------------------------------------
--
-- Adiciona um nó na lista
--
--------------------------------------------------------------------------------
_add = function(nodename)
  local errcode = servermanager.ERROR_CODE
  if not servermanager._nodes[nodename] then
    local new_node = {}
    ---
    local FUNCTION = type( function() end )
    local STRING = type("")
    local NUMBER = type(0)
    local TABLE = type({})
    local sfunc = PATTERNS.get_node_static_data
    local numcpus = 1
    local memory = {errcode, errcode}
    -- local byteorder = servermanager.LITTLE_ENDIAN

    if type(sfunc) == FUNCTION then
      -- numcpus, memory, byteorder = sfunc(nodename)
      numcpus, memory = sfunc(nodename)
      if type(numcpus) ~= NUMBER or numcpus < 1 then
        numcpus = 1
        servermanager.writeError( "Falha de aquisição de número de cpus!")
      end
      if type(memory) ~= TABLE or #memory ~= 2 or
        type(memory[1]) ~= NUMBER or type(memory[2]) ~= NUMBER or
        memory[1] < 0 or memory[2] < 0 then

        memory = {errcode, errcode}
        servermanager.writeError( "Falha de aquisição de memória!")
      end
      -- if type(byteorder) ~= STRING or
      --   (byteorder ~= "LITTLE_ENDIAN" and byteorder ~= "BIG_ENDIAN") then

      --   byteorder = servermanager.LITTLE_ENDIAN
      --   servermanager.writeError( "Falha de aquisição de byteorder!")
      -- else
      --   if byteorder == "LITTLE_ENDIAN" then
      --     byteorder = servermanager.LITTLE_ENDIAN
      --   else
      --     byteorder = servermanager.BIG_ENDIAN
      --   end
      -- end
    else
      servermanager.writeError("Falha de aquisição de dados estáticos.")
    end

    new_node.numcpus = numcpus
    new_node.memory = { ram = memory[1], swap = memory[2]}
    -- new_node.byteorder = byteorder
    new_node.last_update = 0
    ---
    servermanager._nodes[nodename] = new_node
  end -- new node

  return servermanager._nodes[nodename]
end -- function add

------------------------------------------------------------------------------
-- _update(server, maxload)
-- O parâmetro
-- maxold indica o tempo máximo que a informação pode ter. Caso maxold seja
-- nil, é assumido zero e a informação será buscada no servidor em questão.
--
-- Campos estáticos do nó:
-- numcpus
-- memory
--          { ram = xxx, swap = yyy }
-- byteorder
--          LITTLE_ENDIAN ou BIG_ENDIAN.
--
-- Campos dinâmicos do nó:
-- cpuload
-- memoryload
--          { ram = xxx, swap = yyy }
------------------------------------------------------------------------------
_update = function(node, freq)
   local nodeinfo = servermanager._nodes[node]
   if freq and (servermanager.now() - nodeinfo.last_update) < freq then
      return nodeinfo
   end

   nodeinfo.last_update = servermanager.now()
   local FUNCTION = type(function() end)
   local NUMBER = type(0)
   local TABLE = type({})
   local dfunc = PATTERNS.get_node_dynamic_data
   local mload, load_perc = {errcode, errcode} , {errcode, errcode, errcode}

   if type(dfunc) == FUNCTION then
      mload, load_perc = dfunc( node)

      if type(mload) ~= TABLE or #mload ~= 2 or
         type(mload[1]) ~= NUMBER or mload[1] < 0 or
         type(mload[2]) ~= NUMBER or mload[2] < 0 then
        servermanager.writeError( "Falha de aquisição de carga da memória!")
        mload = {errcode, errcode}
      end
      if type(load_perc) ~= TABLE or #load_perc ~= 3 or
         type(load_perc[1]) ~= NUMBER or load_perc[1] < 0 or
         type(load_perc[2]) ~= NUMBER or load_perc[2] < 0 or
         type(load_perc[3]) ~= NUMBER or load_perc[3] < 0 then
        load_perc = {errcode, errcode, errcode}
        servermanager.writeError( "Falha de aquisição de carga de CPU!")
      end
   end

   -- número de jobs = número total de comandos em execução
   local njobs = 0
   for i, v in pairs(COMMAND.array) do
      njobs = njobs + 1
   end

   nodeinfo.cpuload = load_perc[1] / nodeinfo.numcpus
   nodeinfo.memoryload = {ram = mload[1], swap = mload[2]}
   nodeinfo.njobs = njobs

   return nodeinfo
end -- function _update

writeMsg = function(str)
  local msg = "SGAD ["..os.date("%d/%m/%y - %H:%M:%S").."] - "..str
  print(msg)
  io.flush()
end -- function writeMsg

-- writeError = writeMsg
-- writeCmd = writeMsg

_buildErrMsg = function(keys)
  if #keys < 1 then
    return nil
  else
    local errorMessage =
      "Error while retriving configuration for the following keys : ["
    for i, k in ipairs(keys) do
      if i < #keys then
        errorMessage = errorMessage .. k .. ", "
      else
        errorMessage = errorMessage .. k .. "]."
      end
    end
    return errorMessage
  end
end -- function _buildErrMsg

-- Estrutura de um nó
--  node = {
--    numcpus,
--    memory {
--      ram,
--      swap,
--    },
--    cpuload,
--    memoryload {
--      ram,
--      swap,
--    },
--    njobs,
-- }

--------------------------------------------------------------------------------
-- Retorna o número de processadores no servidor especificado. Caso o servidor
-- seja nil, assume localhost. Em caso de erro, retorna nil e a mensagem de
-- erro.
--
-- cpus, err = getnumcpus(server)
--------------------------------------------------------------------------------
_getNumCPU = function(server)
  ninfo = servermanager._getnode(server)

  if not ninfo then
    return nil, servermanager.NO_SERVER
  end

  if not ninfo.numcpus or ninfo.numcpus < 0 then
    return nil, "Não foi possível obter o número de processadores da "..server
  end

  return ninfo.numcpus, nil
end -- function _getNumCPU

--------------------------------------------------------------------------------
-- Retorna a quantidade em bytes de memória no servidor especificado.
-- O retorno é uma tabela contendo o total de memória RAM e de Swap,
-- no formato:
--    { ram = xxx, swap = yyy }
--
-- Caso o servidor seja nil, assume localhost.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- mem, err = getmemory(server)
--------------------------------------------------------------------------------
_getMemory = function(server)
   ninfo = servermanager._getnode(server)

   if not ninfo then
      return nil, servermanager.NO_SERVER
   end

   if not ninfo.memory or
      not ninfo.memory.ram or ninfo.memory.ram < 0 or
      not ninfo.memory.swap or ninfo.memory.swap < 0 then

      return nil, "Não foi possível obter a memória da "..server
   end

   return ninfo.memory, nil
end -- function _getMemory

--------------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de CPU do último minuto no servidor
-- especificado. Esse valor considera o número de processadores que o
-- servidor possui.
--
-- Por exemplo, caso a métrica de ocupação seja o número de processos
-- na fila de prontos, este número estará dividido pela quantidade
-- de processadores. Caso o servidor seja nil, assume localhost.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada
-- no servidor em questão.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- load, err = getcpuload(server, maxold)
--------------------------------------------------------------------------------
_getCPULoad = function (server, maxold)
  ninfo = servermanager._getnode(server)
  if not ninfo then
    return nil, servermanager.NO_SERVER
  end

  servermanager._update(server, maxold)

  if not ninfo.cpuload or ninfo.cpuload < 0 then
    return nil, "Falha ao obter a carga de CPU da "..server
  end

  return ninfo.cpuload, nil
end -- function _getCPULoad

--------------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de memória do último minuto no servidor
-- especificado. O retorno é uma tabela contendo o total de memória RAM e de
-- Swap, no formato:
--   { ram = xxx, swap = yyy }
--
-- Caso o servidor seja nil, assume localhost.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada
-- no servidor em questão.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- load, err = getmemoryload(server, maxold)
--------------------------------------------------------------------------------
_getMemoryLoad = function(server, maxold)
  ninfo = servermanager._getnode(server)

  if not ninfo then
    return nil, servermanager.NO_SERVER
  end

  servermanager._update(server, maxold)

  if not ninfo or not ninfo.memoryload or
    not ninfo.memoryload.ram or ninfo.memoryload.ram < 0 or
    not ninfo.memoryload.swap or ninfo.memoryload.swap < 0 then

    return nil, "Não foi possível obter a carga de memória da "..server
  end

  return ninfo.memoryload, nil
end -- function _getMemoryLoad

--------------------------------------------------------------------------------
-- Retorna o número de jobs que estão em execução no servidor
-- especificado.
--------------------------------------------------------------------------------
_getNumberOfJobs = function(server, maxold)
  ninfo = servermanager._getnode(server)

  if not ninfo then
    return nil, servermanager.NO_SERVER
  end

  servermanager._update(server, maxold)

  if not ninfo.njobs then
    return nil, "Falha de aquisição do número de jobs!"
  end

  return ninfo.njobs, nil
end -- function _getNumberOfJobs

--------------------------------------------------------------------------------
-- Retorna as ação diponiveis para comandos. São as ações aceitas pela função
-- control.
--------------------------------------------------------------------------------
_getCommandActions = function()
  return {
   "SUSPEND",
   "RESUME",
   "HOLD",
   "RELEASE",
   "TERMINATE"
  }
end

--------------------------------------------------------------------------------
-- Retorna o identificador do processo recebido na função
-- executecommand.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- id, err = getcommandid(handle)
--------------------------------------------------------------------------------
_getCommandId = function(handle)
  if type(handle) ~= "table" or not handle.cmdid then
    return nil, "Handle inválido."
  end

  return handle.cmdid, nil
end -- function _getCommandId

--------------------------------------------------------------------------------
-- Retorna um identificador unico do processo iniciado através da função
-- executecommand.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- pid, err = getcommandpid(handle)
--------------------------------------------------------------------------------
_getCommandPid = function(handle)
  if type(handle) ~= "table"
    or not handle.pid
    or not tonumber(handle.pid) then

    return nil, "Handle inválido."
  end

  return handle.pid, nil
end -- function _getCommandPid

--------------------------------------------------------------------------------
-- Retorna o estado de um processo iniciado através da função executecommand.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada no
-- servidor que estiver executando o comando.
--
-- O retorno pode ser RUNNING, NOT_RESPONDING ou FINISHED.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- status, err = _getCommandStatus(handle, maxold)
--------------------------------------------------------------------------------
_getCommandStatus = function(handle, maxold)
  if type(handle) ~= "table" or
    not handle.pid or not tonumber(handle.pid) or
    not handle.last_update or not tonumber(handle.last_update) then
    return nil, "Handle inválido."
  end

  if (not maxold) or ((servermanager.now() - handle.last_update) > maxold) then
    PROCESSES:readProcessesInformation()
    handle.last_update = servermanager.now()
  end

  local idx = PROCESSES:getIndex(handle.pid)
  if idx then
    local state = PROCESSES:getState(idx)
    if state == PROCESS_RUNNING or state == PROCESS_SLEEPING
      or state == PROCESS_WAITING then
      return servermanager.RUNNING, nil
    end
  end
  -- Se o comando não aparece no 'ps' assumimos que ele terminou.
  return servermanager.FINISHED, nil
end -- function _getCommandStatus

--------------------------------------------------------------------------------
-- Retorna  a maquina que esta executando o comando definido por handle.
-- O parâmetro maxold indica o
-- tempo máximo que a informação pode ter. Caso maxold seja nil, é assumido
-- zero e a informação será buscada no servidor que estiver executando o
-- comando. Em caso de erro, retorna nil e a mensagem de erro.
--
-- host, err = getcommandexechost(handle, maxold)
--------------------------------------------------------------------------------
_getCommandExecHost = function(handle, maxold)
  if type(handle) ~= "table" then
    return nil, "Handle inválido."
  end

  if handle.host then
    return handle.host, nil
  end

  return nil, "host unknown"
end -- function _getCommandExecHost

--------------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de CPU do último minuto pelo processo
-- especificado. Esse valor considera o número de processadores que o
-- servidor possui. Por exemplo, caso o processo execute em apenas um
-- processador, este número estará dividido pela quantidade de processadores.
-- Caso o servidor seja nil, assume localhost.
--
-- O parâmetro maxold indica o tempo máximo que a informação pode ter.
-- Caso maxold seja nil, é assumido zero e a informação será buscada
-- no servidor que estiver executando o comando.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- load, err = _getCommandCPULoad(handle, maxold)
--------------------------------------------------------------------------------
_getCommandCPULoad = function(handle, maxold)
  if type(handle) ~= "table" or
    not handle.pid or not tonumber(handle.pid) or
    not handle.last_update or not tonumber(handle.last_update) then
    return nil, "Handle inválido."
  end
  if (not maxold) or ((servermanager.now() - handle.last_update) > maxold) then
    PROCESSES:readProcessesInformation()
    handle.last_update = servermanager.now()
  end

  local idx = PROCESSES:getIndex(handle.pid)
  if not idx then
    return nil, "Comando não encontrado."
  end

  local cpuload = PROCESSES:getCPUPerc(idx)
  if not cpuload then
    return nil, "Não foi possível obter a carga de cpu do comando "..handle.pid
  end

  return cpuload, nil
end -- function getcommandcpuload

--------------------------------------------------------------------------------
-- Obtém o tempo final de execução de um comando
--------------------------------------------------------------------------------
_getCommandFinalTime = function(handle, maxold)
  if type(handle) ~= "table" or
    not handle.pid or not tonumber(handle.pid) or
    not handle.last_update or not tonumber(handle.last_update) then
    return nil, "Handle inválido."
  end

  if maxold and handle.time_update ~= nil and
    ((servermanager.now() - handle.time_update) < maxold) then
    return handle.timeusage, nil
  end

  local func = PATTERNS.time_command_function
  local tmpfile = handle.time_file
  local str = ""

  local fd = io.open(tmpfile, "r")
  if not fd then
    servermanager.writeError( "Erro ao abrir arquivo temporário:\n"..
      "\t["..tostring(tmpfile).."]\n"..
      "\tpara aquisição de dados de execução\n" )
  else
    str = fd:read("*a")
    fd:close()
  end
  local real, user, system, output = func( str )
  real = tonumber(real) or errcode
  user = tonumber(user) or errcode
  system = tonumber(system) or errcode

  if string.len(output) > 0 then
    local errormsg = string.format(
      "Possível erro na execução do comando %s:\n%s",
      handle.pid, output)
    servermanager.writeError(errormsg)
  end
  if system == errcode and real == errcode and user == errcode then
     return nil, "Não foi possível obter o tempo final de execucao do "..
                 "comando " .. handle.pid
  end

  local info = {
    user = user,
    system = system,
    elapsed = real,
  }
  handle.timeusage = info
  handle.time_update = servermanager.now()

  return info, nil
end -- function _getCommandFinalTime

--------------------------------------------------------------------------------
-- Retorna a taxa média de ocupação de memória do último minuto pelo processo
-- especificado. O retorno é uma tabela contendo o total de memória RAM e de
-- Swap, no formato:
--     { ram = xxx, swap = yyy }
---------------------------------
-- MAS QUAL A UNIDADE?!?! Bytes?!
---------------------------------
-- O parâmetro maxold indica o tempo máximo que a informação pode ter. Caso
-- maxold seja nil, é assumido zero e a informação será buscada no servidor
-- que estiver executando o comando. Em caso de erro, retorna nil e a mensagem
-- de erro.
--
-- load, err = getcommandmemoryload(handle, maxold)
--------------------------------------------------------------------------------
_getCommandMemoryLoad = function (handle, maxold)
  if type(handle) ~= "table" or
    not handle.pid or not tonumber(handle.pid) or
    not handle.last_update or not tonumber(handle.last_update) then
    return nil, "Handle inválido."
  end

  if (not maxold) or ((servermanager.now() - handle.last_update) > maxold) then
    PROCESSES:readProcessesInformation()
    handle.last_update = servermanager.now()
  end

  local idx = PROCESSES:getIndex(handle.pid)

  if not idx then
    return nil, "Comando não encontrado."
  end

  local mem_used = PROCESSES:getMemoryUsed(idx)
  if not mem_used or type(mem_used) ~= "table" or
    not mem_used.ram or not mem_used.swap then
    return nil, "Não foi possível obter a carga da memória "..
      "utilizada pelo comando "..handle.pid
  end

  return mem_used, nil
  end -- function _getCommandMemoryLoad

--------------------------------------------------------------------------------
-- Retorna os tempos de execução do processo especificado. O retorno é uma
-- tabela contendo os tempos de usuário, sistema e total (tempo de parede),
-- no formato:
--               { user = xxx, system = yyy, elapsed = zzz }
-- O parâmetro maxold indica o tempo máximo que a informação pode ter. Caso
-- maxold seja nil, é assumido zero e a informação será buscada no servidor
-- que estiver executando o comando.
--
-- Em caso de erro, retorna nil e a mensagem de erro.
--
-- time, err = getcommandtimeusage(handle, maxold)
--------------------------------------------------------------------------------
_getCommandTimeUsage = function (handle, maxold)
  local errcode = servermanager.ERROR_CODE
  if type(handle) ~= "table" or
    not handle.pid or not tonumber(handle.pid) or
    not handle.last_update or not tonumber(handle.last_update) then
    return nil, "Handle inválido."
  end

  if (not maxold) or ((servermanager.now() - handle.last_update) > maxold) then
    PROCESSES:readProcessesInformation()
    handle.last_update = servermanager.now()
  end

  local idx = PROCESSES:getIndex(handle.pid)
  if not idx then
    return servermanager._getCommandFinalTime(handle, maxold)
  end

  local state = PROCESSES:getState(idx)
  if state == PROCESS_FINISHED then
    return servermanager._getCommandFinalTime(handle, maxold)
  end

  local system = PROCESSES:getCPUTime(idx) or errcode
  local real = PROCESSES:getWallTime(idx) or errcode
  if system == errcode and real == errcode then
    return nil, "Não foi possível obter o tempo de execucao do "..
      "comando " .. handle.pid
  end
  local info = {
    user = system,
    system = system,
    elapsed = real,
  }

  return info, nil
end -- function _getCommandTimeUsage

--------------------------------------------------------------------------------

servermanager = {
  -------------
  -- private --
  -------------
  NO_SERVER = "Servidor não existente", -- Colocar num arquivo de mensagens?
  _nodes = {},
  _add = _add,
  _getCommandFinalTime = _getCommandFinalTime,
  _getnode = _getnode,
  _update  = _update,
  _create_process = _create_process,
  -------------
  -- public --
  -------------
  now = now,
  sleep = sleep,
  writeCmd = writeMsg,
  writeError = writeMsg,
  writeMsg = writeMsg,

  -- Funções de controle do módulo:
  open = open,
  close = close,

  -- Funções de consulta à configuração de servidor:
  -- setDefaultConfigKeys = setDefaultConfigKeys,
  -- getAllConfigKeys = getAllConfigKeys,
  getConfiguration = getConfiguration,

  -- Funções de monitoração de servidor:
  -- setDefaultInfoKeys = setDefaultInfoKeys,
  -- getAllInfoKeys = getAllInfoKeys,
  getInfo = getInfo,

  -- Funções de execução, monitoração e controle de processos:
  control = control,
  executeCommand = executeCommand,
  getCommandInfo = getCommandInfo,
  getCommandPersistentData = getCommandPersistentData,
  releaseCommandResources = releaseCommandResources,
  retrieveCommandHandle = retrieveCommandHandle,

  -- @TODO Retirar quando a solução de "job of jobs" for implementada
  getJobsInfo = getJobsInfo,

  -- Funções de log de histórico
  setHistoric = setHistoric,
  getHistoric = getHistoric,

  -- Constantes do módulo:
  RUNNING = 0,
  NOT_RESPONDING = 1,
  WAITING = 2,
  FINISHED = 3,

  LITTLE_ENDIAN = 0,
  BIG_ENDIAN = 1,

  ERROR_CODE = -1,
}