[P]El pid de un proceso hijo [Aun más Largo]

emhn at telcel.net.ve emhn at telcel.net.ve
Sat Jun 24 18:02:47 CDT 2000


On Sat, 24 Jun 2000, L. Alejandro Bernal R. wrote:
>Buena realmente lo que estoy haciendo es un CGI que permite a un estudiante
>enviar un programa en C para ser calificado por su profesor, pero antes de que
>le llegue al profesor este pasa por una serie de flitros, el primero es el
>compilador, el segundo son una serie de pruebas de caja negra que son preparadas
>por el profesor y es hay donde esta el problema, pues un estudiante puede
>enviar un programa que puede quedar en un cliclo infinito. Simplificando el
>problema  un minimo de codigo, el problema se puede resumir de la siguiente
>forma:

Interesante problema pero (en mi opinión), muy impráctico de atacar con C,
sobre todo si se trata de un CGI. Por cierto, copio a caracas-pm-list at pm.org
porque estoy seguro que les interesa ;-)

Perl es _el_ lenguaje por excelencia para escribir CGIs, y en tu caso
particular combina todas las herramientas necesarias para interactuar con
procesos externos y hacer la evaluación. Me voy a permitir plantearte una
solución alternativa utilizando Perl (pero no voy a hacer la parte CGI :-).

Tenemos inicialmente el programa suma.pl. Es un programa muy simple que lee
números desde la entrada standard y emite su suma una vez que la ha consumido.
Para simular la posibilidad de que el programa se quede "colgado", si el número
a sumar es 42, entonces se queda en un ciclo infinito. Este es el programa:

#!/usr/bin/perl
while (<>) {
  $sum += $_;
  1 while ($_ == 42);   # Por qué 42? Porque es la respuesta a todo ;-)
}
print $sum,"\n";

Muy simple y cumple con tus requisitos: recibe sus datos por la entrada
standard, emite sus resultados por la salida standard y puede quedarse
"colgado" en algún momento.

¿Cómo hará el profesor para "probar" el programa? Primero ha de escribir las
pruebas de "caja negra" en un archivo de texto. Yo lo llamaré "examen.txt" y
tiene un formato muy sencillo: cada línea tiene un nombre para el caso, el
resultado esperado y los datos que deben suministrarse al programa, todo
separado con comas y longitud variable. Para nuestro ejemplo, uso lo siguiente:

Primer Caso, 10, 1, 2, 3, 4
Otro mas, 6, 1, 2, 3
Esto es para que falle a proposito, 3, 2, 2
Seguimos probando, 16, 7, 5, 4, -5, 5
Este se cuelga, 64, 2, 42, 10, 8, 2
Nunca llegara, 666, 111, 222, 333, 999, -888, -111

Nótese que tengo un ejemplo para que falle a propósito (es más fácil que
modificar suma.pl para que lo haga :-).

Dado que el profesor probablemente querrá que el programa califique
automáticamente y de forma manejable, definiré que el resultado de la
evaluación sea un archivo cuyos contenidos serán: el nombre del caso y la
cadena "Ok", "Falla" o "Excedido en tiempo" según sea conveniente. Este archivo
lo he llamado "calificacion.txt" en el programa evaluador.

Ahora el jugoso programa de evaluación testing.pl. El programa adjunto no es
parametrizable (pero se deja como ejercicio para el lector), ni hace labor CGI
(pero el módulo CGI de Perl será suficiente). Básicamente combina los conceptos
que presentaste en C, pero de una manera más clara y limpia. Por cierto, el
programa puede escribirse para que sea mucho más breve y eficiente, pero eso
oscurecería un poco la lógica. Aquí va:

#!/usr/bin/perl

use IPC::Open2;  # open2, permite abrir _dos_ pipes contra un mismo proceso

my $limite = 5;               # Cada prueba se destruirá en 5 segundos
my $programa = "./suma.pl";   # Programa a probar

open(E,"examen.txt");         # De aquí leeremos los casos de prueba
open(C,">calificacion.txt");  # Aquí escribiré los resultados

# Vamos a usar una señal de alarma SIGALRM igual que en tu ejemplo en C
# (pero esto es _mucho_ más conciso :-)

$SIG{ALRM} = sub { die "timeout" };    # Si llega la alarma genero una
                                       # excepcion que dice "timeout"

while (<E>) {    # Leemos el siguiente caso de prueba
  chomp;         # Fuera el \n al final de la línea
  ($caso,$esperado, at argumentos) = split /,/;   # Quito las "," y asigno.
  eval {         # Ejecutar y _salvar_ excepciones ;-)
    alarm($limite);     # Tiene $limite segundos para responder
    open2(*RESULTADOS,*PARAMETROS,$programa)    # Magia negra explicada abajo
    foreach $a (@argumentos) {   # Para cada argumento...
       print PARAMETROS "$a\n";  # ... enviarlo al STDIN del programa
    }
    close(PARAMETROS);           # El programa hijo corre (*).
    $resultado = <RESULTADOS>;   # Leo la línea resultado.
    chomp($resultado);           # Le quito el \n
    close(RESULTADOS);           # Termino de leer, muere el programa hijo.
    if ($resultado == $esperado) {
      print C "$caso, Ok\n";     # Coinciden los resultados
    } else {
      print C "$caso, Falla\n";  # Error en el programa de prueba
    }
    alarm(0);                    # Deshabilito la alarma
  };
  if ($@) {  # Ocurrió alguna excepción?
    if ($@ =~ /timeout/) {       # Se excedió en tiempo
      print C "$caso, Excedido en tiempo\n";   # Indicar el evento
      close(PARAMETROS);         # Estos archivos aún están abiertos, así
      close(RESULTADOS);         # que los cierro (el programa hijo morirá)
    } else {  # Ocurrió otro error
      alarm(0);   # Deshabilito la alarma
      die;        # Muero indicando el error
    }
  }
  # Si no hubo excepciones, el while continua al siguiente caso de prueba
}
close(E);   # Cerramos todo porque hemos terminado
close(C);

Eso es to, eso es to, eso es todo amigos!

La magia negra del open2 vale todo el programa: open2 ejecuta el programa hijo
y abre dos pipes, uno para escribir conectado a la entrada standard del hijo y
otro para leer conectado a la salida standard del hijo. Si se cierran ambos
pipes, el hijo sabe que debe terminar. La "extraña" notación *F quiere decir
que la rutina open2 debe _asociar_ los pipes físicos a los nombres indicados
con asterisco (se llama un typeglob en la jerga Perl).

Anexo los programas y archivos de ejemplo sin comentarios para que no tengan
que escribirlos :-)

Nótese que el programa hijo realmente comienza a correr una vez que ha recibido
_toda_ su entrada standard (la línea marcada con (*)); si la alarma se dispara
antes de terminar, salta directo a la rutina que genera la excepción, el "eval"
se interrumpe y se detecta inmediatamente.

Perl hace obsoletos todos los lenguajes modernos, es postmodernista :-)
--
Ernesto Hernández-Novich - Running Linux 2.2.14 - Unix: Live free or die!
One thing is to be the best, and another is to be the most popular.
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCS d+(-) s+: a C+++$ UBLAVHIOSC++++$ P++++$ L+++$ E- W+ N++ ?o ?K w--- O- M- V
PS+ PE Y+ PGP>++ t+ 5 X+ R* tv+ b++ DI+++$ D++ G>++ e++ h+ r+ y++
-----END GEEK CODE BLOCK-----


-------------- next part --------------
A non-text attachment was scrubbed...
Name: hack.tar.gz
Type: application/x-gzip
Size: 801 bytes
Desc: not available
Url : http://mail.pm.org/archives/caracas-pm/attachments/20000624/2df01a82/hack.tar.bin


More information about the caracas-pm mailing list