/*
 * Este programa comprueba de 2 formas diferentes si existe un proceso
 * con un PID dado.
 *
 * Sintaxis: ./existe_pid [<pid1> <pid2> ... <pidN>]
 * Si no se especifican PIDs escoge una lista predeterminada
 * y la comprueba
 * Si si se especifican comprueba los PIDs dados
 * En cualquier caso, despues de la lista, comprueba que su
 * propio PID que tiene asignado existe.
 *
 * El resultado lo muestra en la salida estandar
 *
 * La idea es desarrollar distintos metodos de comprobacion, sobre
 * todo que sean portables. Por esta razon el mejor metodo es el indicado
 * al final, que hace uso de la funcion kill.
 *
 * No requiere opciones especiales de compilacion, p.ej.:
 * gcc -o existe_pid existe_pid.c
 *
 * Distintas personas han colaborado dando ideas sobre como
 * desarrollar estas funciones.
 * Miguel de Dios en la lista del hacklab de salamanca sugirio
 * leer el codigo fuente del paquete procps
 * <http://procps.cvs.sourceforge.net/procps/procps/>
 * Este paquete contiene el programa ps, que en linux funciona
 * leyendo de /proc la informacion de cada proceso.
 * Eduardo Minguez Perez en la lista de SalamancaWireless
 * indico que en OpenBSD hay procesos que guardan su PID en respectivos
 * ficheros de /var/run/. Esto mismo ocurre tambien en linux, y el problema
 * que se presenta en ambos casos es que no todos los procesos guardan su PID
 * alli.
 * Jose Angel de Bustos en la lista de Glisa dio a conocer la macro
 * for_each_process, definida en linux/sched.c; Abajo comento algo mas sobre este
 * metodo, pero no lo he implementado.
 * Jose Luis Jimenez en la lista Gnu-C de Hispalinux comento la maniobra
 * mas jugosa: usar kill, y segun el valor que retorne discernir si existe o no.
 * Es la primera solucion que se me ocurrio, aunque presentaba algunas dudas
 * respecto a que senal usar.
 * Aparte de usar kill, se me ocurrio mirar directamente en /proc si existe
 * un fichero correspondiente al PID dado. Esto funciona si el kernel proporciona
 * este servicio en /proc, cosa que si ocurre en la mayoria de los linux y en irix
 * (aunque con formato diferente), y posiblemente en Solaris.
 * Otra forma de acceder a /proc es mediante libproc, que trata de abstraer el acceso
 * a este sistema de ficheros.
 *
 * Finalmente, solo he implementado dos metodos, el de kill y el de mirar en /proc
 * a mano. Personalmente el de kill me vale, y para la finalidad de ver si existe un PID
 * creo que no necesito andar buscando otro, salvo que descubra algun problema de
 * portabilidad.
 *
 * Autor: Pablo Garcia-Figuerola <perki_pat3#yahoo!es>, noviembre de 2006.
 * http://blog.lafalaciadelabarbaverde.com/?p=32
 * http://blog.lafalaciadelabarbaverde.com/
 * Licencia: GPLv2, por poner alguna :P
 *
 */

#undef NDEBUG
#include <assert.h>

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

int existe_pid_proc(pid_t);
/*int existe_pid_libproc(pid_t);	/* No la he llegado a
				   desarrollar*/
/*int existe_pid_macros(pid_t);	/*Da algunos problemas y 
				  no llega a compilar,
				  ver mas abajo */
int existe_pid_kill(pid_t);

int
main(int argc, char **argv)
{
  int i,j;
  pid_t lista_pids_const[]={1,237,1916}; //FIX se debe usar el 1?
  pid_t *lista_pids;
  int num_pids,num_func;
  int (*existe[])(pid_t)={	/* Anadir aqui nuevas funciones */
    existe_pid_proc,
    existe_pid_kill,
  };
  char *metodo[]={		/* Y aqui su descripcion, en el mismo orden */
    "proc",
    "kill",
  };

  num_pids=(argc-1)?(argc-1):(sizeof(lista_pids_const)/sizeof(pid_t));
  
  lista_pids=(pid_t *)calloc(num_pids+1,sizeof(pid_t));
  if(!lista_pids) {
    perror("Error al reservar memoria");
    exit(EXIT_FAILURE);
  }

  for(i=0;i<num_pids;i++)
    if(argc-1)			/* Usar la lista dada, o si no hay, una por defecto */
      lista_pids[i]=(pid_t)atoi(argv[i+1]);
    else
      lista_pids[i]=lista_pids_const[i];
  lista_pids[num_pids++]=getpid(); /* El ultimo siempre existe */

  num_func=sizeof(existe)/sizeof(int(*)(pid_t));
  for(i=0;i<num_pids;i++) {
    printf("PID: %u:\n",lista_pids[i]);	/* dado un pid de la lista */
    for(j=0;j<num_func;j++) {	/* y un metodo */
      printf("\tmetodo: %s -> %s existe.\n",metodo[j],
	     ( existe[j](lista_pids[i]) )?"si":"no");
    }
  }

  exit(EXIT_SUCCESS);
}

/*
 * Comprueba si en /proc existe una entrada para el pid dado
 * En caso afirmativo o en caso de error devuelve 1. Si no
 * existe devuelve 0.
 *
 * Usa las funciones scandir() y alphasort() definidas en dirent.h
 * stdio.h: snprintf()
 * string.h: strncmp()
 * Constantes: PID_S_LEN
 *
 * Probablemente solo funcione en Linux con el kernel compilado con
 * soporte para procfs. Quiza tambien en Solaris, con alguna modifi
 * cacion en IRIX.
 */
int
existe_pid_proc(pid_t pid)
{
#define PID_S_LEN 10		/* Que quepa el PID en un string */
  struct dirent **entradas;
  int num_entradas;
  char pid_s[PID_S_LEN];
  int retornar=0;

  snprintf(pid_s,PID_S_LEN,"%u",pid);
  num_entradas=scandir("/proc",&entradas,0,alphasort); /*ver el manual*/
  if(num_entradas<0)
    return 1;
  while(num_entradas-->0) {
    if( !retornar && 0==strncmp(pid_s,entradas[num_entradas]->d_name,PID_S_LEN) )
      retornar=1; /*si hay una entrada en /proc para ese pid*/
    free(entradas[num_entradas]);
  }
  free(entradas);

  return retornar;
#undef PID_S_LEN
}

/*
 * Tambien se puede acceder a /proc mediante libproc
 * lo cual proporciona un interfaz unico independientemente
 * de la plataforma.
 *
 * Desgraciadamente, apenas he encontrado documentacion sobre
 * libproc, tan solo el codigo fuente en el CVS de OpenSolaris
 * donde parece que estan atacando bastante este tema de
 * acceder a todo /proc de una forma uniforme.
 *
 * En Debian etch a fecha de noviembre de 2006 hay un paquete
 * llamado libproc-dev, que probablemente fuera de utilidad,
 * pero debido a problemas de dependencias y versiones no
 * lo he instalado.
 int existe_pid_libproc(pid_t proceso)
 {
   return 0;
 }
 *
 * Tampoco se hasta que punto presenta interoperabilidad
 * con distintos sistemas operativos o configuraciones
 * del kernel.
 *
 */

/*
 * Jose Angel de Bustos en la lista de Glisa propuso el uso
 * de la macro for_each_process, definida en linux/sched.h
 *
 * La funcion quedaria mas o menos de esta forma (no esta
 * comprobada):
 *
 int existe_pid_macros(pid_t proceso) 
 { 
   struct task_struct *tarea;

   for_each_process(tarea) {
     if(proceso==tarea->pid)
       return 1;
   }

   return 0;
 }
 *
 * He obtenido algunos problemas al compilar, por
 * dependencias de linux/sched.h.
 * No estoy seguro de si es solo usable desde
 * el kernel, o al menos con unas opciones de compilacion
 * poco comunes.
 *
 * Dudo mucho de su portabilidad a otros sistemas operativos,
 * pese a que no requiera acceso a /proc
 *
 */

/*
 * Metodo basado en la funcion kill
 *
 * Envia la senal 0 al proceso dado. Suponemos que es
 * una senal valida en cualquier entorno.
 *
 * Puede ocurrir que el proceso exista y tengamos permiso
 * para enviarle la senal, con lo que kill devolvera 0, y
 * esta funcion devolvera 1
 *
 * Puede ser que directamente no exista: kill devuelve -1 y
 * errno=ESRCH. Devolveremos 0
 *
 * Puede ser que si exista, pero que no tengamos permiso
 * para enviarle una senal, en cuyo caso kill devolvera
 * -1 y errno=EPERM
 * Puede ser que exista, pero la senal que enviemos no sea
 * valida, kill devolvera -1 y errno=EINVAL. Hemos supuesto
 * que la senal que enviamos es valida, luego este caso no
 * deberia darse (assert).
 * De estos dos ultimos casos se concluye que el proceso si
 * existe, y por tanto se devuelve -1.
 *
 * Los PIDs menores de 1 son ignorados, devolviendo 0 ya que
 * kill reserva comportamientos especiales que no pretendemos
 * usar para la finalidad de esta funcion.
 *
 * FIX: se debe permitir enviar una senal al PID 1 (init)
 *
 * Ver en el manual kill(2)
 *
 * Requiere sys/types.h, unistd.h, errno.h y assert.h
 * Para no depurar se debe definir NDEBUG antes de incluir assert.h
 *
 * Deberia de ser la opcion mas portable
 *
 * Esta opcion fue desarrollada con ayuda de Jose Luis Jimenez,
 * de la lista de GNU-C de Hispalinux
 */
int existe_pid_kill(pid_t proceso)
{
  int ret;

  if(proceso<1)
    ret=0;
  else
    if(0==kill(proceso, 0))
      ret=1; /*todo bien, luego existe*/
    else
      if(ESRCH==errno)
	ret=0; /*no existe*/
      else {
	assert(errno!=EINVAL);
	ret=1; /*no tenemos permiso (luego existe), o senal invalida*/
      }
  
  return ret;
}

