#!/bin/ksh
#
# Uso: primeiro argumento deve ser a lista de pipes a serem criados
#      todos os outros argumentos serao comandos a serem executados
#
# Estrategia: para cada comando, o script cria um novo processo ao
#             entrar na funcao execute. Esse novo processo atua como
#             monitor - ele dispara o comando em si e 
#             aguarda seu retorno. Caso haja falha, ele provoca
#             a queda do processo pai (reacao em cadeia forcada
#             pelo killChildren)


# --------------------------------------------------------------------------
# CONFIGURACAO PARA DESENVOLVIMENTO
# --------------------------------------------------------------------------
# Defina debugfile para um arquivo se desejar debug, deixe em branco 
# para desligar debug

debugfile=""
#debugfile=${HOME}/flowmonitor.debug


signals="TERM INT HUP QUIT FPE SEGV BUS ABRT"

# --------------------------------------------------------------------------
# TRAP DE SINAIS
# --------------------------------------------------------------------------
# Garante a execucao da funcao finalize no caso da ocorrencia dos eventos 
# abaixo 
# IMPORTANTE: kill -9 NUNCA pode ser capturado e nao ira passar pelo finalize.
# Por isso, o sga devera matar o precesso com um kill sem parametros 
# (sinal TERM)

trap treatSignal $signals 

# --------------------------------------------------------------------------
# DEFINICAO DE FUNCOES
# --------------------------------------------------------------------------

# Funcao de limpeza (apaga os pipes). Ela nao esta incorporada ao 
# finalize porque ela sera chamada mesmo quando o comando termina com
# sucesso
cleanup () {
  debug "`cat $lockFile 2>/dev/null`"
  rm -f $fifos 
  rm -f $lockFile
  rm -f $psFile
  debug "DELETEI OS ARQUIVOS TEMPORARIOS (PIPES, LOCKFILE E PSFILE)"
}

# Funcao obtem todos os processos filhos de $1. 
# Para isso, ela atua recursivamente sobre o snapshot da memoria que esta 
# no arquivo $psFile para obter todos os processos. 
# O resultado eh armazenado na variavel global $pidList, que deve ser 
# zerada pelo chamador da funcao.
getAllChildPids() {
  typeset childrenPids=`cat $psFile | $AWK '{ if ($3 == param) print $2 }' param=$1`
  typeset childPid
  typeset newChild
  for childPid in $childrenPids; do
    newChild=`cat $psFile | grep -v flowmonitor | $AWK '{ if ($2 == param) print $2 }' param=$childPid`
    pidList="$pidList $newChild"
  done
  for childPid in $childrenPids; do
    getAllChildPids $childPid
  done 
}


# Funcao que analisa o fim do comando com indice $1. 
# Caso ele tenha provocado erro e seja o primeiro a fazer isso, 
# a funcao coleta todos os processos filhos do rootPid e os mata. 
# Caso contrario, ela nao faz nada
treatCmdError() {
  # Desliga o trap para evitar loop infinito 
  trap - $signals
  typeset cmdIndex=$1
  typeset cmdString=$2
  typeset firstIndexToStop=`grep -v '^0' $lockFile 2>/dev/null | head -n1 | cut -d: -f2 `
  typeset cmdRetCode=`grep -v '^0' $lockFile 2>/dev/null | head -n1 | cut -d: -f1`
  debug "$cmdIndex O primeiro a morrer com falha foi:$firstIndexToStop"
  if [ "$cmdIndex" == "$firstIndexToStop" ];then
    debug "$cmdIndex Entrou na finalizacao efetiva"
    log "comando causador da falha (indice $cmdIndex, codigo de erro $cmdRetCode): [$cmdString]"
    if [ -n "$CSBASE_FLOW_GUILTY_NODE_ID_LOG" ]; then
        echo "$cmdIndex" > $CSBASE_FLOW_GUILTY_NODE_ID_LOG
    fi
    ps $PS_ARGS > $psFile
    pidList=""
    getAllChildPids $rootPid
    debug "$cmdIndex Killing allChildPids: [`echo $pidList`]"
    kill $pidList 2>/dev/null
  else
    debug "$cmdIndex Nao entrou na finalizacao efetiva"
  fi
}

# Funcao para tratar sinais enviados ao flowmonitor. 
# Caso seja enviado um dos sinais $signals, ela determina todos os 
# processos filhos de $rootPid e os mata. Em seguida, faz a limpeza.
treatSignal() {
  # Desliga o trap para evitar loop infinito 
  trap - $signals
  # Garante que nenhum processo vai se considerar o primeiro a morrer
  echo "-1:0" >> $lockFile
  debug "0 Entrou no tratamento de sinais"
  log "comando terminado por captura de sinal"
  ps $PS_ARGS > $psFile
  pidList=""
  getAllChildPids $rootPid
  debug "0 allChildPids: [`echo $pidList`]"
  kill $pidList 2>/dev/null
  #Aguarda a morte efetiva dos processos para fazer o cleanup
  debug "0 Vai esperar pela finalizacao dos processos [`echo $pidList`]"
  for pid in $pidList; do
    wait $pid
  done
  debug "0 Terminou de esperar, agora pode fazer o cleanup"
  cleanup
}

# Funcao executada no processo monitor: ela dispara o comando e aguarda seu
# retorno. Se o retorno nao for zero, chama o finalize
execute () {
  typeset index=$1
  typeset command=$2
  ksh -c "$command"
  echo "$?:$index" >> $lockFile
  treatCmdError "$index" "$command"
}

# Funcao de controle de debug, imprime o paramentro do arquivo de debug, 
# caso ele exista
debug () {
  typeset message=$1
  if [ -n "$debugfile" ];then
    echo "$message" >>$debugfile
  fi
}

# Funcao auxiliar para apagar o arquivo de debug e garantir que um debug 
# nao se mistura com o proximo
resetDebug() {
  if [ -f $debugFile ]; then
    rm -f $debugfile
  fi
}

log() {
  typeset message=$1
  echo "[flowmonitor] $message" >&2
}

# ----------------------------------------------------------------------------
# DEFINICAO DE VARIAVEIS
# ----------------------------------------------------------------------------
resetDebug

# String com os pipes (NAO inclui caminho)
fifos=$1
shift

cmdIds=$1
shift

# Pid do processo principal. Todos os outros serao filhos ou netos dele.
# Esse eh o processo a ser derrubado caso haja falha
rootPid=$$
debug "PID: $rootPid"

# Gera os arquivos de controle.
#  - No lockFile serao escritos os numeros dos comandos terminados, em
#    ordem cronologica (o primeiro na lista é o que parou antes) 
#  - No psFile sera armazenado o "snapshot" da memoria que sera utilizado
#    durante a interrupcao de todos os prossos filhos de rootPid
lockFile=`mktemp XXX`
psFile=`mktemp XXX`
debug "LOCKFILE: $lockFile"
debug "PSFILE: $psFile"

# Determina os comandos awk e argumentos do ps para a plataforma corrente
if [ `uname -s` = "Linux" ] || [ `uname -s` = "Darwin" ] ;
then
  whence awk > /dev/null 2>&1 || {
    log "ERRO: awk nao pode ser encontrado no PATH em `hostname`"
    exit 1
  }
  AWK="awk"
  PS_ARGS="-ewo user,pid,ppid,args"
else
  whence nawk > /dev/null 2>&1 || {
    log "ERRO: nawk nao pode ser encontrado no PATH em `hostname`"
    exit 1
  }
  AWK="nawk"
fi

if [ `uname -s` = "Linux" ] || [ `uname -s` = "Darwin" ];
then
  PS_ARGS="-ewo user,pid,ppid,args"
else
  PS_ARGS="-eo user,pid,ppid,args"
fi

# Pids a serem monitorados. O processo que faz o tee NAO deve
# ser monitorado.
monitored_pids=""

# ----------------------------------------------------------------------------
# INICIO DO SCRIPT
# ----------------------------------------------------------------------------

# Cria pipes. Se pipe nao for criado, interrompe processo
if [ -n "$fifos" ]; then
  mkfifo $fifos || exit 1
fi

for id in $cmdIds ; do
  if [ -n "$1" ]; then
    cmd=$1 || exit 1
  fi
  execute $id "$1"& 
  pid="$!"
  
  if [ $pid -ge 0 ] ; then
    # Acrescenta o pid do novo monitor a lista a ser monitorada
    monitored_pids="$monitored_pids $pid"
  fi

  shift
done

# Espera pelo retorno de todos os monitores.
wait $monitored_pids

# Apaga pipes
cleanup
