---------------------------------------------------------------------
-- $Author: fpina $
-- $Revision: 177515 $  - $Date: 2016-11-16 18:03:54 -0200 (Wed, 16 Nov 2016) $
---------------------------------------------------------------------
local verbose = require "sga.verbose"
local ext = require "shellExtension"
---------------------------------------------------------------------
-- Tabelas globais
---------------------------------------------------------------------
COMMAND = {}
COMMAND.array = {}
COMMAND.end_table = {}

-- Possiveis estados de um comando
COMMAND.state = {
  RUNNING  = "RUNNING",
  SLEEPING = "SLEEPING",
  WAITING  = "WAITING",
  FINISHED = "FINISHED",
}

COMMAND.ERROR_CODE = -1

----------------------------------------------------------------------
-- States (idl):
--    "RUNNING"
--    "SLEEPING"
--    "WAITING"
--    "FINISHED"
--
-- States (lib):
--    RUNNING
--    WAITING
--    NOT_RESPONDING
--    FINISHED
---------------------------------------------------------------------

---------------------------------------------------------------------
-- Retorna o estado do comando
---------------------------------------------------------------------
function COMMAND:getState(state)
  if state == SGAD_CONF.serverManager.RUNNING then
    return COMMAND.state.RUNNING
  elseif state == SGAD_CONF.serverManager.WAITING then
    return COMMAND.state.WAITING
  elseif state == SGAD_CONF.serverManager.NOT_RESPONDING then
    return COMMAND.state.RUNNING
  else --if state == SGAD_CONF.serverManager.FINISHED then
    return COMMAND.state.FINISHED
  end
end

---------------------------------------------------------------------
-- Adiciona um comando a lista de comandos
---------------------------------------------------------------------
function COMMAND:addCommandToList(cmd)
  if cmd and cmd.id then
    self.array[cmd.id] = cmd
    self:writePersistentData()
  end
end

---------------------------------------------------------------------
-- Envia notificação de término de comando para o servidor
---------------------------------------------------------------------
function COMMAND:SendCompletedInfo(cmd)
  local keys = {
    CONST.COMMAND_WALL_TIME_SEC,
    CONST.COMMAND_USER_TIME_SEC,
    CONST.COMMAND_SYSTEM_TIME_SEC,
  }

  local info
  local time, err = SGAD_CONF.serverManager.getCommandInfo(
    cmd.handle,
    keys,
    SGAD_CONF.completed_time_seconds)

  if not time then
    verbose:error(err)
    info = {
      elapsedTimeSec = self.ERROR_CODE,
      userTimeSec = self.ERROR_CODE,
      cpuTimeSec = self.ERROR_CODE,
    }
  else
    info = {
      elapsedTimeSec = time[CONST.COMMAND_WALL_TIME_SEC],
      userTimeSec = time[CONST.COMMAND_USER_TIME_SEC],
      cpuTimeSec = time[CONST.COMMAND_SYSTEM_TIME_SEC],
    }
  end

  -- envia notificação ao servidor
  local ssi = SGA_DAEMON.ssi

  local suc, stat, exception =
    pcall(ssi.commandCompleted, ssi, SGA_DAEMON:getHostName(), cmd, cmd.id, info)
  if not suc then
    if exception and type(exception) ~= "string" then
      local exceptMsg = ""
      if exception._repid == sgaidl.types.InvalidSGAException then
        exceptMsg = exception.message
      elseif exception._repid == sgaidl.types.InvalidCommandException then
        exceptMsg = exception.message
      elseif exception._repid == sgaidl.types.NoPermissionException then
        exceptMsg = exception.message
      elseif exception._repid == corbasysex.TRANSIENT then
        exceptMsg = exception.completed .. exception.minor
      else
        -- error(exception) -- rethrow
      end
      verbose:debug(exceptMsg)
    end
    verbose:error("Falha no envio de notificação de fim de comando.")
    return nil
  end
  -- Se o servidor rejeitou a notificação, não adianta enviar novamente.
  if not stat then
    verbose:error("Falha na notificação de fim de comando.6")
  end

  verbose:cmdcomp("Tempo decorrido ("..
    tostring(cmd.id).."): "..
    tostring(info.elapsedTimeSec)..", "..
    tostring(info.userTimeSec)..", "..
    tostring(info.cpuTimeSec))

  verbose:cmdcomp("Desativando o servant CORBA!")
  cmd:deactivate()

  return true
end

---------------------------------------------------------------------
-- Persiste a tabela de comandos
---------------------------------------------------------------------
function COMMAND:writePersistentData()
   local ptable = {}

   for id, cmd in pairs(self.array) do
      if cmd and type(cmd) == type({}) then
        local server = SGAD_CONF.serverManager
        local libdata, err = server.getCommandPersistentData(cmd.handle)
        if err then
          verbose:error(err)
        else
            local pdata = {
              command = cmd.command,
              path = cmd.path,
              output_path = cmd.output_path,
              id = cmd.id,
              host = cmd.host,
              libdata = libdata,
              sandbox_paths = cmd.sandbox_paths,
              extraParams = cmd.extraParams,
            }
            table.insert(ptable, pdata)
        end
      else
        verbose:error(
          string.format("Referência do comando %s está nula.", tostring(id)))
      end
   end

   local succeeded, fd = pcall(io.open, SGAD_CONF.dump_file, "w")
   if not succeeded or not fd then
      verbose:error(
        string.format("O arquivo de persistência %s não pode ser aberto.",
          SGAD_CONF.dump_file))
      if (fd) then
        -- fd neste caso é a mensagem de erro
        verbose:error(fd)
      end
      return
   end
   local succeeded, err = pcall(fd.write, fd, "return ".. COMMAND:tostring(ptable))
   if not succeeded then
      verbose:error("Não foi possível gravar o arquivo de persistência.")
      verbose:error(err)
      return
   end
   succeeded, err = pcall(fd.close, fd)
   if not succeeded then
      verbose:error("Não foi possível fechar o arquivo de persistência.")
      verbose:error(err)
      return
   end
end

---------------------------------------------------------------------
-- Carrega os dados dos comandos
---------------------------------------------------------------------
function COMMAND:loadPersistentData()
  -- Carrega o arquivo de persistência de comandos em execução
  local func = loadfile(SGAD_CONF.dump_file)
  local ptable

  -- Executa o arquivo carregado se não houve erro
  if func then
    ptable = func()
  end

  -- Arquivo inválido, comandos perdidos
  if type(ptable) ~= type({}) then
    verbose:error("Não foi possível ler o arquivo de persistência.")
    ptable = {}
  end

  self.end_table = {}

  local nc = #ptable
  for c = 1, nc do
    local pdata = ptable[c]
    local cmd = {}

    if pdata and type(pdata) == 'table' then
      local server = SGAD_CONF.serverManager
      local handle, err = server.retrieveCommandHandle(pdata.libdata)
      if err then
        verbose:error(err)
        if CSFS.launch_daemon == YES and pdata.id == CSFS.process_id then
          CSFS.process = nil
        elseif SGAD_CONF.net_benchmark == YES and pdata.id == IPERF.process_id then
          IPERF.process = nil
        else
          table.insert(self.end_table, pdata)
        end
      else
        setmetatable(cmd, CommandClass.meta)
        cmd.parent = CommandClass
        cmd.command = pdata.command
        cmd.path = pdata.path
        cmd.output_path = pdata.output_path
        cmd.id = pdata.id
        cmd.host = pdata.host
        cmd.handle = handle
        cmd.expired = SGAD_CONF.process_time_seconds
        local commandInfo, err =
          SGAD_CONF.serverManager.getCommandInfo(handle, {CONST.COMMAND_PID})
        cmd.main_pid = commandInfo[CONST.COMMAND_PID]
        cmd.sandbox_paths = pdata.sandbox_paths
        cmd.extraParams = pdata.extraParams
        if not commandInfo then
          cmd.main_pid = 0
        end
        verbose:cmdretr("Comando recuperado:\n"..
          "\tComando: ["..cmd.command.."]\n"..
          "\tId: ["..cmd.id.."]\n"..
          "\tHost: ["..tostring(cmd.host).."]\n" ..
          "\tPID: ["..tostring(cmd.main_pid).."].")
        self:addCommandToList(cmd)
        if CSFS.launch_daemon == YES and pdata.id == CSFS.process_id then
          CSFS.process = cmd
        elseif SGAD_CONF.net_benchmark == YES and pdata.id == IPERF.process_id then
          IPERF.process = cmd
        end
      end
    else
      verbose:debug("Referência nula para comando no arquivo de persistência.")
    end
  end
  self:notifyRetrievedItems()
end

---------------------------------------------------------------------
-- Notifica o servidor com os comandos recuperados
---------------------------------------------------------------------
function COMMAND:notifyRetrievedItems()
  -- Envia notificação de termino de comandos que estavam no arquivo
  -- de persistencia, mas nao foram recuperados.
  for i = 1, #self.end_table do
    local ssi = SGA_DAEMON.ssi
    local end_cmd = self.end_table[i]
    if end_cmd and type(end_cmd) == type({}) then
      verbose:cmdretr(
        "Comando não recuperado. Enviando notificação - Id: " ..
        tostring(end_cmd.id))

      local suc, stat =
        pcall(ssi.commandLost, ssi, SGA_DAEMON:getHostName(), end_cmd.id)

      if not suc or not stat then
        if exception and type(exception) ~= "string" then
          local exceptMsg = ""
          if exception._repid == sgaidl.types.InvalidSGAException then
            exceptMsg = exception.message
          elseif exception._repid == sgaidl.types.InvalidCommandException then
            exceptMsg = exception.message
          elseif exception._repid == sgaidl.types.NoPermissionException then
            exceptMsg = exception.message
          elseif exception._repid == corbasysex.TRANSIENT then
            exceptMsg = exception.completed .. exception.minor
          else
            -- error(exception) -- rethrow
          end
          verbose:debug(exceptMsg)
        end
        verbose:error("Falha no envio de notificação de comando não recuperado.")
        return
      end
    end
  end

  local rtable = {}
  for id, cmd in pairs(self.array) do
    if cmd and type(cmd) == type({}) then
        -- Não enviar notificação de recuperação ao servidor
        -- se o comando for o CSFS.
        if ((not (CSFS.launch_daemon == YES and id == CSFS.process_id)) and
          (not (SGAD_CONF.net_benchmark == YES and id == IPERF.process_id))) then
        table.insert(rtable, {cmdId = cmd.id, cmdRef = cmd})
      end
    else
      verbose:error("\t"..tostring(id).." - NULO!")
    end
  end

  -- Envia comandos recuperados
  local ssi = SGA_DAEMON.ssi

  local suc, stat =
    pcall(ssi.commandRetrieved, ssi, SGA_DAEMON:getHostName(), rtable)

  if not suc or not stat then
    if exception and type(exception) ~= "string" then
      local exceptMsg
      if exception._repid == sgaidl.types.InvalidSGAException then
        exceptMsg = exception.message
      elseif exception._repid == sgaidl.types.InvalidCommandException then
        exceptMsg = exception.message
      elseif exception._repid == sgaidl.types.NoPermissionException then
        exceptMsg = exception.message
      elseif exception._repid == corbasysex.TRANSIENT then
        exceptMsg = exception.completed .. exception.minor
      else
        -- error(exception) -- rethrow
      end
      verbose:debug(exceptMsg)
    end
    verbose:error("Falha no envio de comando recuperado.")
    return
  end
end

---------------------------------------------------------------------
-- Procura por comandos finalizados e notifica o servidor
---------------------------------------------------------------------
function COMMAND:searchCompletedItems()
  -- Acumulamos os comandos terminados
  -- e enviamos as mensagens para o SSI de uma vez!
  local end_table = {}
  for id, cmd in pairs(self.array) do
    if cmd and type(cmd) == "table" then
      local server = SGAD_CONF.serverManager
      local handle = cmd.handle
      local comandInfo, err =
        server.getCommandInfo(handle, {CONST.COMMAND_PID, CONST.COMMAND_STATE})
      local pid = comandInfo[CONST.COMMAND_PID]
      local state = comandInfo[CONST.COMMAND_STATE]
      if not comandInfo then
        verbose:error(err)
        pid = 0
        state = server.NOT_RESPONDING
      end
      if state == server.FINISHED then
        verbose:cmdcomp(string.format("Comando %s terminou [%s]", id, pid))
        if CSFS.launch_daemon == YES and cmd.id == CSFS.process_id then
          verbose:cmdcomp("Desativando o servant CORBA!")
          cmd:deactivate()
          CSFS.process = nil
          if CSFS.retries_number then
            if CSFS.retries_number > 0 then
              local retryNumber =
                (SGAD_CONF.csfs_retries_number - CSFS.retries_number) + 1
              verbose:cmdcomp(
                string.format("Reiniciando CSFS: %s de %s tentativas.",
                  retryNumber, SGAD_CONF.csfs_retries_number))
              CSFS.retries_number = CSFS.retries_number - 1
            else
              verbose:kill("Não foi possível iniciar o CSFS.")
              SGA_DAEMON:exit(0)
            end
          end
          SGA_DAEMON:createCSFSProcess()
        elseif SGAD_CONF.net_benchmark == YES and cmd.id == IPERF.process_id then
          verbose:cmdcomp("Desativando o servant CORBA!")
          cmd:deactivate()
          IPERF.process = nil
          SGA_DAEMON:createIPerfProcess()
        else
          table.insert(end_table, cmd)
        end
      else
        -- Não exibe mensagem quando o comando é o CSFS ou IPERF
        if cmd.id == CSFS.process_id then
          CSFS.retries_number = SGAD_CONF.csfs_retries_number
        elseif cmd.id ~= IPERF.process_id then
          verbose:cmdcomp(
            string.format("Comando %s está em execução [%s].", id, pid))
        end
      end
    else
      verbose:cmdcomp(
        string.format("Referência para o comando %s está nula.", id))
    end
  end

  -- Envia as notificações
  for i = 1, #end_table do
    local end_cmd = end_table[i]
    if end_cmd and type(end_cmd) == "table" then
      verbose:cmdcomp("Notificação de término ao SSI - pid: "..
        tostring(end_cmd:getPid()))
      if not self:SendCompletedInfo(end_cmd) then
        verbose:error("Erro ao notificar o ssi do final do comando "..end_cmd.id)
      end
    end
  end

  return true
end

---------------------------------------------------------------------
-- Serializa os elementos de Lua em uma string
---------------------------------------------------------------------
function COMMAND:tostring(val)
  local t = type(val)

  if t == "table" then
    local f, s = next(val, nil)
    local str = '{'
    while f do
      if not tonumber(f) then
        str = str .. "[\"" .. f .. "\"]=" .. self:tostring(s)
      else
        str = str .. "["   .. f .. "]="   .. self:tostring(s)
      end
      str = str .. ","
      f, s = next(val, f)
    end
    return str .. '}'
  elseif t == "string" then
    return "[[" .. val .. "]]"
  elseif t == "number" then
    return val
  else -- if not tab then
    return "nil"
  end
end -- function COMMAND:tostring


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

---------------------------------------------------------------------
-- Função que define o mecanismo de herança com o campo 'parent'
---------------------------------------------------------------------
function inherit_from_parent(tab, index)
   if type(tab) ~= type({}) then
      error("indexed expression is not a table.")
   end

   local parent = rawget(tab, "parent")
   if parent then
      return parent[ index ]
   else
      return nil
   end
end


----------------------------------------------------------------------
-- Tabela que implementa a interface SGACommand
----------------------------------------------------------------------
CommandClass = {}
CommandClass.meta = {
   __index = inherit_from_parent
}

----------------------------------------------------------------------
-- Executa o comando na máquina selecionada
----------------------------------------------------------------------
function Command(sga_command)
  if not sga_command.command then
    verbose:error("Comando nulo abortado!")
    return nil
  end

  -----------------------------------------------------------
  -- Configura a meta table para disponibilizar as funções --
  -- de gerenciamento de comandos                          --
  -----------------------------------------------------------
  setmetatable(sga_command, CommandClass.meta)
  sga_command.parent = CommandClass
  -----------------------------------------------------------

  sga_command.command = tostring(sga_command.command)
  if not sga_command.id then
    verbose:error("Comando sem identificador abortado!")
    return nil
  end
  sga_command.id = tostring(sga_command.id)
  -----------
  -- START --
  -----------
  local start= ext.createDirectoryChooser()
  start:add(sga_command.path)    -- diretório de Binários do Algoritmo
  start:add(SGAD_CONF.ssibindir) -- diretório de Binários do Framework
  start:add(SGAD_CONF.sgabindir) -- diretório do Binário do SGA
  local startdir = start:choose("start")

  if startdir then
    local id = sga_command.id
    -- Preciso colocar os parâmetros numa variável por causa do ";".
    local paramcmd = string.gsub(sga_command.command, "([;<>\(\)])","\\%1")
    local sandbox = sga_command:getSandboxDir()
    local startcmd = [["%sstart" %s "%s"; %s]]
    sga_command.command =
      string.format(startcmd, startdir, id, sandbox,paramcmd)
  end
  ---------
  -- Verifica se a maquina escolhida existe ou escolhe uma maquina.
  local host
  if sga_command.host then
    host = string.upper(sga_command.host)
  end
  if host then
    if host ~= string.upper(SGA_DAEMON:getName()) then
      local nodes = SGA_DAEMON:getNodes()
      local found = false

      for k,tab in ipairs(nodes) do
        if string.upper(tab[CONST.SGA_NODE_NAME]) == host then
          found = tab[CONST.SGA_NODE_NAME]
          break
        end
      end

      -- if not found
      if not found then
        local errmsg = "No configuration found for host: "..sga_command.host
        verbose:error("\n\n"..errmsg.."!!!\n\n")
        return nil, errmsg
      end
      host = found
    else
      host = nil
    end
  -- elseif SGA_DAEMON:isCluster() and not SGAD_CONF.deny_node_selection then
  --   local nodes = SGA_DAEMON:getNodesDynamicInfo()
  --   local minAvg = nodes[1].load_avg_perc.loadAvg1min
  --   host = nodes[1].name

  --   for i,v in pairs(nodes) do
  --     if type(v) == "table" and v.load_avg_perc.loadAvg1min >= 0 and
  --       v.load_avg_perc.loadAvg1min < minAvg then
  --       minAvg = v.load_avg_perc.loadAvg1min
  --       host = v.name
  --     end
  --   end
  end

  sga_command.host = host or SGA_DAEMON:getName()

  if sga_command.sandbox_paths then
    -- Cria sandboxes de execução do comando
    for i = 1, #sga_command.sandbox_paths do
      local sandbox = sga_command:getSandboxDir(i)
      verbose:debug("Criando diretório de sandbox "..sandbox)
      if ext.isDir(sandbox) then
        verbose:error("Diretório de sandbox "..sandbox.." do comando "..sga_command.id.." já existe! ")
      else
        if not ext.mkdirs(sandbox) then
        	verbose:error("Erro ao criar o diretório de sandbox "..sandbox.." do comando "..sga_command.id)
          return nil
        end
      end
    end
  else
    if ((not (CSFS.launch_daemon == YES and sga_command.id == CSFS.process_id)) and
      (not (SGAD_CONF.net_benchmark == YES and sga_command.id == IPERF.process_id))) then
      verbose:error("Sandbox de execução do comando "..sga_command.id.." não está disponível! ")
    end
  end

  -- executeCommand = function(id, command, extraParams, server)
  sga_command.extraParams[CONST.COMMAND_INPUT_PATH] = startdir
  sga_command.extraParams[CONST.COMMAND_EXECUTION_OUTPUT_PATH] = sga_command.output_path
  sga_command.extraParams[CONST.COMMAND_ERROR_PATH] = err_output

  -- Escolheu uma maquina para a execucao do comando...
  local handle, err =
    SGAD_CONF.serverManager.executeCommand(
      sga_command.id,
      sga_command.command,
      sga_command.extraParams,
      host)
  if err then
    verbose:error(string.format(
      "Erro ao executar comando %s: %s",
      sga_command.command, err))
    return nil
  else
    -- sga_command.main_pid, err = SGAD_CONF.serverManager.getCommandPid(handle)
    local comandInfo, err = SGAD_CONF.serverManager.getCommandInfo(handle, {CONST.COMMAND_PID})
    sga_command.main_pid = comandInfo[CONST.COMMAND_PID]
    if not commandInfo then
       sga_command.main_pid = 0
    end

    sga_command.handle = handle
    sga_command.expired = SGAD_CONF.process_time_seconds
    verbose:cmdexec(
      "Comando para execução:\n"..
      "\tComando: ["..sga_command.command.."]\n"..
      "\tId: ["..sga_command.id.."]\n"..
      "\tHost: ["..tostring(sga_command.host).."]\n" ..
      "\tPID: ["..tostring(sga_command.main_pid).."].")
  end
  return sga_command
end -- function Command

----------------------------------------------------------------------
-- Retorna o caminho completo para o diretório da sandbox de execução
-- do comando para um determinado índice ou retorna o caminho para a
-- primeira sandbox se não for passado nenhum índice
----------------------------------------------------------------------
function CommandClass:getSandboxDir(index)
  return self.sandbox_paths[index or 1]
end

----------------------------------------------------------------------
-- Define valores padrão para as informações de um processo.
----------------------------------------------------------------------
function CommandClass:setDefaults(defaults)
  self.defaults = defaults
end

----------------------------------------------------------------------
-- Preenche as informações de um processo com valores padrão.
----------------------------------------------------------------------
function CommandClass:fillDefaults(tab)
  for field, value in pairs(self.defaults) do
     if not tab[field] then
       tab[field] = value
     end
  end
end

----------------------------------------------------------------------
-- Retorna as informações sobre o status do comando
----------------------------------------------------------------------
function CommandClass:parseCommandInfo(filename)
  local tab = {}
  local attrs = {}
  local nitems = 0 -- Faz o papel do getn, que não funciona neste caso.
  local insertInfo = function()
    if nitems < 1 then
      return
    end
    self:fillDefaults(attrs)
    local validState = false
    for i, v in pairs(COMMAND.state) do
      if attrs.state == v then
        validState = true
        break
      end
    end
    if not validState then
      attrs.state = COMMAND.state.FINISHED
    end
    table.insert(tab, attrs)
    attrs = {}
    nitems = 0
  end
  for line in io.lines(filename) do
    local emptyline = true
    for i, v in string.gmatch(line, "(%w+)%s*=%s*([%.%w]+)") do
      attrs[i] = tonumber(v) or v
      emptyline = false
      nitems = nitems + 1
    end
    if emptyline then
      insertInfo()
    end
  end
  insertInfo()
  return tab
end

----------------------------------------------------------------------
-- Retorna o PID do comando em execução
----------------------------------------------------------------------
function CommandClass:getPid()
   return self.main_pid
end

----------------------------------------------------------------------
-- Retorna o estado de execução do comando
----------------------------------------------------------------------
function CommandClass:isRunning()
   local server = SGAD_CONF.serverManager
   -- return server.getcommandstatus(self.handle, self.expired) == server.RUNNING
   local commandInfo, err = server.getCommandInfo(self.handle, {CONST.COMMAND_STATE}, self.expired)
   return commandInfo[CONST.COMMAND_STATE] == server.RUNNING
end

function CommandClass:isWaiting()
   local server = SGAD_CONF.serverManager
   -- return server.getcommandstatus(self.handle, self.expired) == server.WAITING
   local commandInfo = server.getCommandInfo(self.handle, {CONST.COMMAND_STATE}, self.expired)
   return commandInfo[CONST.COMMAND_STATE] == server.WAITING
end

----------------------------------------------------------------------
-- Retorna as informações sobre o comando
----------------------------------------------------------------------
function CommandClass:getRunningCommandInfo()
  local infos = self:collectProcessInfo()
  local extra = self:collectExecutionData()

  local infoDic = {}

  for _, info in ipairs(infos) do
    table.insert(infoDic, ext.mapToDictionary(ext.convertCommandInfo(info)))
  end

  local commandInfo = {
    processData = infoDic,
    executionData = extra,
  }

  return commandInfo
end

----------------------------------------------------------------------
-- Coleta informações adicionais sobre o comando em execução.
----------------------------------------------------------------------
function CommandClass:collectExecutionData()
   local attrs = {}
   local status= ext.createDirectoryChooser()
   status:add(self.path)           -- diretório de Binários do Algoritmo
   status:add(SGAD_CONF.ssibindir) -- diretório de Binários do Framework
   status:add(SGAD_CONF.sgabindir) -- diretório do Binário do SGA
   local statusdir = status:choose("collect_execution_data")
   if statusdir then
     local id = self.id or self.main_pid
     --local outputfile = os.tmpname()
     local outputfile = ".tmpFile."..SGA_DAEMON:getName()..id
     local sandbox = self:getSandboxDir()
     ext.execCmdLine(ext.SHELL_SH, "'"..statusdir.."collect_execution_data'", outputfile, false, id, "'"..sandbox.."'")
     for line in io.lines(outputfile) do
       for key, value in string.gmatch(line, "(.-)%s*=%s*([^,]+)") do
         table.insert(attrs, { key = key, value = value })
       end
     end
     os.remove(outputfile)
   end

   return attrs
end

----------------------------------------------------------------------
-- Coleta os dados do comando em execução.
----------------------------------------------------------------------
function CommandClass:collectProcessInfo()
  local infodefault = {}
  -- infodefault.csbase_command_id = 0,
  infodefault.processorId = 0
  infodefault[CONST.COMMAND_PID] = self.main_pid
  infodefault[CONST.COMMAND_PPID] = 0
  infodefault[CONST.COMMAND_STRING] = self.command
  infodefault[CONST.COMMAND_EXEC_HOST] = "unknown"
  infodefault[CONST.COMMAND_STATE] = COMMAND.state.FINISHED
  infodefault[CONST.COMMAND_MEMORY_RAM_SIZE_MB] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_MEMORY_SWAP_SIZE_MB] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_CPU_PERC] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_CPU_TIME_SEC] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_WALL_TIME_SEC] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_USER_TIME_SEC] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_SYSTEM_TIME_SEC] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_VIRTUAL_MEMORY_SIZE_MB] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_BYTES_IN_KB] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_BYTES_OUT_KB] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_DISK_BYTES_READ_KB] = COMMAND.ERROR_CODE
  infodefault[CONST.COMMAND_DISK_BYTES_WRITE_KB] = COMMAND.ERROR_CODE

  self:setDefaults(infodefault)

  ------------
  -- STATUS --
  ------------
  local status = ext.createDirectoryChooser()
  status:add(self.path)           -- diretório de Binários do Algoritmo
  status:add(SGAD_CONF.ssibindir) -- diretório de Binários do Framework
  status:add(SGAD_CONF.sgabindir) -- diretório do Binário do SGA
  local statusdir = status:choose("status")
  if statusdir then
    local id = SGAD_CONF.serverManager.getcommandid(self.handle) or self.main_pid
    --local output = os.tmpname()
    local output = ".tmpFile."..SGA_DAEMON:getName()..id
    local sandbox = self:getSandboxDir()
    ext.execCmdLine(ext.SHELL_SH, "'"..statusdir.."status'", output, false, id, "'"..sandbox.."'")
    local tab = self:parseCommandInfo(output)
    os.remove(output)
    if #tab == 0 then
      tab = { infodefault }
    end

    return tab
  end

   --
   -- @TODO Adicionar no retorno da função getCommandInfo uma tabela com os filhos do comando

  if (not self:isRunning()) and (not self:isWaiting()) then -- XXX Precisa verificar?
    local server = SGAD_CONF.serverManager
    local commandInfo = server.getCommandInfo(
      self.handle,
      {CONST.COMMAND_STATE, CONST.COMMAND_EXEC_HOST},
      self.expired)
    local status = commandInfo[CONST.COMMAND_STATE]
    local ehost = commandInfo[CONST.COMMAND_EXEC_HOST]
    verbose:warning("PID não encontrado:\n" .. "\t[" ..self.command ..
      "] "..tostring(self.main_pid) ..".")
    infodefault.execHost = ehost or infodefault.execHost
    if status then
      infodefault.state = COMMAND:getState(status)
    end

    return { infodefault }
  end

  local server = SGAD_CONF.serverManager
  local commandInfo, err = server.getCommandInfo(
    self.handle,
    {CONST.COMMAND_EXEC_HOST},
    self.expired)
  local ehost = commandInfo[CONST.COMMAND_EXEC_HOST]
  if not commandInfo then
    verbose:error(err)
    ehost = "unknown"
  end

  local commandInfo, err = server.getCommandInfo(self.handle, nil, self.expired)
  self:fillDefaults(commandInfo)

  return {commandInfo}
  -- return commandInfo
end

----------------------------------------------------------------------
-- Retorna um referência para o SGA
----------------------------------------------------------------------
function CommandClass:getSGAServer()
  return SGA_DAEMON.servant
end

----------------------------------------------------------------------
-- Termina a execução do comando
----------------------------------------------------------------------
function CommandClass:control(action, childId)
  verbose:debug("Cancelando o comando "..self.id)
  ----------
  -- STOP --
  ----------
  local stop= ext.createDirectoryChooser()
  stop:add(self.path)           -- diretório de Binários do Algoritmo
  stop:add(SGAD_CONF.ssibindir) -- diretório de Binários do Framework
  stop:add(SGAD_CONF.sgabindir) -- diretório do Binário do SGA
  local stopdir = stop:choose("stop")
  if stopdir then
    local id =
      SGAD_CONF.serverManager.getcommandid(self.handle) or
      self.main_pid
    local sandbox = self:getSandboxDir()
    ext.execCmdLine(
      ext.SHELL_SH,
      "'"..stopdir.."stop'",
      nil,
      false,
      id,
      "'"..sandbox.."'")
  else
    verbose:debug("Chama api específica "..self.id)
    local ok, err = SGAD_CONF.serverManager.control(self.handle, action, childId)
    verbose:debug("Volta api específica "..self.id)
    if err then
      verbose:error(err)
    end
  end

  ---------

  verbose:cmdkill("Desativando o servant CORBA para comando abortado: "..self.id)
  verbose:debug("Chama deactivate "..self.id)
  self:deactivate()
  verbose:debug("Terminou deactivate "..self.id)
end

----------------------------------------------------------------------
-- Desativa o servant CORBA.
-- Necessário para liberar a referência Lua
----------------------------------------------------------------------
function CommandClass:deactivate()
  SGA_DAEMON.orb:deactivate(self, "IDL:sgaidl/SGACommand:1.0")

   verbose:debug("Atualizando a persistência "..self.id)
   COMMAND.array[self.id] = nil
   COMMAND:writePersistentData()
   if self.sandbox_paths then
      for i = 1, #self.sandbox_paths do
         local sandbox = self:getSandboxDir(i)
         verbose:debug("Removendo diretório de sandbox "..sandbox)
         if not ext.isDir(sandbox) then
            verbose:warning("Diretório de sandbox "..sandbox.." do comando "..self.id.." não encontrado (pode já ter sido removido pelo comando).")
         else
            if not ext.rmdir(sandbox) then
               verbose:error("Erro ao remover de sandbox "..sandbox.." do comando "..self.id)
      	    end
         end
      end
   end
   verbose:debug("Atualizou a persistência "..self.id)
   SGAD_CONF.serverManager.releaseCommandResources(self.handle)
   verbose:debug("Liberou área do comando "..self.id)
end

----------------------------------------------------------------------
-- Imprime toda a tabela
----------------------------------------------------------------------
function _dump(tb)
   for i,cmd_tb in ipairs(tb) do
      for idx,val in pairs(cmd_tb) do
         print(idx,val)
      end
   end
end
