]> git.llucax.com Git - software/sercom-old.git/blob - src/sc_test
Detalles.
[software/sercom-old.git] / src / sc_test
1 #!/usr/bin/php
2 <?php // vim: set binary noeol et sw=4 sts=4:
3
4 require_once 'T/general.php';
5
6 define('R_ERR', 0);
7 define('R_OK',  1);
8
9 $LOGLEVEL = DEBUG;
10
11 $gconf = $CONF['general'];
12
13 // Obtengo id de usuario con el cual ejecutar las pruebas.
14 if (!($usrinfo = @posix_getpwnam($gconf['user']))) {
15     logserr("No existe el usuario '{$gconf['user']}'");
16     exit(10); // salgo con error
17 }
18 $UID = $usrinfo['uid'];
19 $GID = $usrinfo['gid'];
20 /*if (!@posix_seteuid($usrinfo['uid'])) {
21     logserr("No se puede cambiar el uid efectivo a '{$usrinfo['uid']}'");
22     exit(11); // salgo con error
23 }*/
24
25 $intento = new T_Intento;
26
27 // Sin cesar.
28 while (1) {
29     if (!($mail = $intento->proximo_a_probar())) {
30         logs('No hay intento para probar', DEBUG);
31         sleep($gconf['intervalo']);
32         continue;
33     }
34     logs('Nuevo intento a probar (' . $intento->to_line() . ')');
35
36     $intento_dir = "{$gconf['data_dir']}/" . $intento->path();
37     $entrega_dir = "{$gconf['data_dir']}/" . $intento->base_path('entregas');
38
39     if (is_readable("$entrega_dir/Makefile")) {
40         $makefile = "$entrega_dir/Makefile";
41 //XXX        @copy("$entrega_dir/Makefile", "$intento_dir/Makefile")
42 //            or logserr("Error al copiar Makefile (de '$entrega_dir/Makefile' a '$intento_dir/Makefile'");
43     // Fallback
44     } elseif (is_readable("{$gconf['data_dir']}/Makefile")) {
45         $makefile = "{$gconf['data_dir']}/Makefile";
46 //XXX    if (!@copy("{$gconf['data_dir']}/Makefile", "$intento_dir/Makefile")) {
47     } else {
48         enviar_respuesta_error_log($mail, 'No hay un Makefile disponible', $intento);
49         continue;
50     }
51
52     $currdir = getcwd();
53     if (!@chdir($intento_dir)) {
54         enviar_respuesta_error_log($mail, 'Error al cambiar al directorio del tp', $intento);
55         continue;
56     }
57     logs("Cambio de directorio '$currdir' -> '$intento_dir'", DEBUG);
58     logs('Ejecutando el comando: make -f '.escapeshellarg($makefile), DEBUG);
59     if (exec_get_info('make -f '.escapeshellarg($makefile), $ret, $err, $out)) {
60         if ($ret) {
61             logs('Error al compilar');
62             logs("Código de retorno: $ret, mensaje: $err)", DEBUG);
63             //XXX $intento->informar_compilacion(false);
64             enviar_respuesta(R_ERR, $mail, "ERROR AL COMPILAR!\n\n$err\n\nCódigo de retorno: $ret\n", $intento);
65             continue;
66         } else {
67             logs('Compilado OK');
68             //XXX $intento->informar_compilacion(true);
69             // TODO mail acumulativo
70             // enviar_respuesta(R_OK, $mail, 'El intento fue compilado con éxito!', $intento);
71         }
72     } else {
73         //XXX $intento->informar_compilacion(false);
74         enviar_respuesta_error_log($mail, 'No se pudo ejecutar make', $intento);
75         continue;
76     }
77     if (!@mkdir('chroot')) {
78         enviar_respuesta_error_log($mail, 'Error al crear directorio para chroot', $intento);
79         continue;
80     }
81     if (!@rename('tp', 'chroot/tp')) {
82         enviar_respuesta_error_log($mail, 'Error al mover el tp al chroot', $intento);
83         continue;
84     }
85     if (!@copy("{$gconf['data_dir']}/redir", 'chroot/redir')) {
86         enviar_respuesta_error_log($mail, 'Error al copiar redireccionador al chroot', $intento);
87         continue;
88     }
89     if ($err = chmod_own_grp('chroot', 02770, $UID, $GID)) {
90         enviar_respuesta_error_log($mail, $err, $intento);
91         continue;
92     }
93     if ($err = chmod_own_grp('chroot/tp', 0550, $UID, $GID)) {
94         enviar_respuesta_error_log($mail, $err, $intento);
95         continue;
96     }
97     if ($err = chmod_own_grp('chroot/redir', 0550, 0, 0)) {
98         enviar_respuesta_error_log($mail, $err, $intento);
99         continue;
100     }
101
102     // comienza la ejecución de casos de prueba
103     while ($prueba = $intento->pedir_caso_de_prueba()) {
104         logs('Prueba: ' . $prueba->to_line(), DEBUG);
105         // ejecuta con fork
106         $pid = pcntl_fork();
107         if ($pid == -1) {
108             // Error al forkear
109             $intento->resultado_de_prueba($prueba, false, 'Error al forkear proceso');
110             enviar_respuesta_error_log($mail, 'Error al forkear proceso', $intento);
111             continue 2;
112         } elseif ($pid) {
113             // Estamos en el padre, controlamos el tiempo.
114             logs("En el padre (hijo: $pid)", DEBUG);
115             // TODO controlar tiempo.
116             pcntl_waitpid($pid, $exitcode);
117             logs("Fin de ejecución de caso de prueba (hijo: $pid, ret: $exitcode)", DEBUG);
118             $stderr = false; // FIXME ver si salida de error es vacia.
119             if ($exitcode or $stderr) {
120                 logs('Comando ejecutado ERROR', DEBUG);
121                 if ($ret) {
122                     $msg = "El programa salió con código de error $ret";
123                     $msgs[] = $msg;
124                     logs($msg, DEBUG);
125                 }
126                 if ($stderr) {
127                     $msg = "El programa imprimió mensajes en la salida de error: '$stderr'";
128                     $msgs[] = $msg;
129                     logs($msg, DEBUG);
130                 }
131                 $msg = join("\n", $msgs);
132                 $intento->resultado_de_prueba($prueba, false, $msg);
133                 enviar_respuesta(R_ERR, $mail, $msg, $intento);
134                 exit(0); // salgo ok del hijo
135             } else { // Sin errores en la ejecución.
136                 logs('Comando ejecutado OK', DEBUG);
137                 // TODO
138                 $salidas = array();
139                 foreach ($prueba->salidas as $salida) {
140                     // TODO hacer diffs.
141                     if ($salida == 'stdout') {
142                         $salidas[$salida] = $stdout;
143                     } else {
144                         $salidas[$salida] = file_get_contents($salida);
145                     }
146                 }
147                 logs('Salidas: ' . var_export($salidas, true), DEBUG);
148                 $intento->resultado_de_prueba($prueba, true, 'Salidas: ' . var_export($salidas, true));
149                 enviar_respuesta(R_ERR, $mail, 'Salidas: ' . var_export($salidas, true), $intento);
150                 exit(0); // salgo ok del hijo
151             }
152         } else {
153             // Estamos en el hijo, corremos en chroot.
154             logs('En el hijo', DEBUG);
155             // Hago chroot.
156             if (!@chroot('chroot')) {
157                 $intento->resultado_de_prueba($prueba, false, 'Error al hacer chroot');
158                 enviar_respuesta_error_log($mail, 'Error al hacer chroot', $intento);
159                 exit(1); // salgo del hijo
160             }
161             logs('Chrooteado, cwd = '.getcwd(), DEBUG);
162             if (!@posix_setuid($usrinfo['uid'])) {
163                 $intento->resultado_de_prueba($prueba, false, "Error al cambiar al uid '{$usrinfo['uid']}'");
164                 enviar_respuesta_error_log($mail, "Error al cambiar al uid '{$usrinfo['uid']}'", $intento);
165                 exit(2); // salgo del hijo
166             }
167             // TODO poner stdout y stderr en archivos para hacer diff o hacer diffs en memoria
168             $stdout = null;
169             if (in_array('stdout', $prueba->salidas)) $stdout = '';
170             logs('Se ejecutará: chroot chroot /tp ' . escapeshellarg($prueba->params), DEBUG);
171             if (!pcntl_exec('/redir', array('tp', 'stdin', 'stdout', 'stderr', '/tp', '0', $prueba->params))) {
172                 logserr('No se pudo ejecutar el comando');
173                 $intento->resultado_de_prueba($prueba, false, 'No se pudo ejecutar el tp');
174                 enviar_respuesta_error_log($mail, 'No se pudo ejecutar el tp', $intento);
175                 exit(4); // salgo del hijo
176             }
177             exit(100); // salgo del hijo (no deberia llegar nunca aca).
178         }
179         break; // FIXME
180     }
181
182     // TODO make clean
183 }
184
185
186 /**
187  * Ejecuta un comando devolviendo el código de error y las salidas.
188  *
189  * @param cmd Comando a ejecutar.
190  * @param ret Código de retorno.
191  * @param err Salida de error (se obtiene sólo si es != null).
192  * @param out Salida estándar (se obtiene sólo si es != null).
193  * @param in  Entrada a enviarle al comando (null si no se le envía nada).
194  * @return true si se ejecutó el comando, false si no.
195  */
196 function exec_fds($cmd, &$ret, $in = false, $out = false, $in = false) {
197     $descriptors = array();
198     if ($in)  $descriptors[0] = get_fd_type($in,  'r');
199     if ($out) $descriptors[1] = get_fd_type($out, 'w');
200     if ($err) $descriptors[2] = get_fd_type($err, 'w');
201 var_dump($descriptors);
202     $proc = proc_open($cmd, $descriptors, $pipes);
203     if (is_resource($proc)) {
204         $ret = proc_close($proc);
205         return true;
206     }
207     return false;
208 }
209
210 /**
211  * Obtiene un tipo de descriptor para proc_open().
212  *
213  * @param fd Tipo de descriptor: si es true, se usa un pipe, si es un array
214  *           se usa el array y si es otra cosa se usa un archivo con este
215  *           nombre.
216  * @param mode Modo de apertura, sólo se usa si es true o string.
217  * @return Array a pasar a proc_open().
218  */
219 function get_fd_type($fd, $mode = false) {
220     if ($fd === true) {
221         return array('pipe', $mode);
222     } elseif (is_array($fd)) {
223         return $fd;
224     }
225     return array('file', $fd, $mode);
226 }
227
228 /**
229  * Ejecuta un comando devolviendo el código de error y las salidas.
230  *
231  * @param cmd Comando a ejecutar.
232  * @param ret Código de retorno.
233  * @param err Salida de error (se obtiene sólo si es != null).
234  * @param out Salida estándar (se obtiene sólo si es != null).
235  * @param in  Entrada a enviarle al comando (null si no se le envía nada).
236  * @return true si se ejecutó el comando, false si no.
237  */
238 function exec_get_info($cmd, &$ret, &$err, &$out, $in = null) {
239     $descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w'));
240     $proc = proc_open($cmd, $descriptors, $pipes);
241     if (is_resource($proc)) {
242         if (is_null($in)) {
243             fputs($pipes[0], $in);
244         }
245         if (is_null($out)) {
246             $out = stream_get_contents($pipes[1]);
247         }
248         if (is_null($err)) {
249             $err = stream_get_contents($pipes[2]);
250         }
251         foreach (array(0,1,2) as $i) {
252             fclose($pipes[$i]);
253         }
254         $ret = proc_close($proc);
255         return true;
256     }
257     return false;
258 }
259
260 function enviar_respuesta($tipo, $to, $mensaje = '', $intento = null) {
261     $mconf = $GLOBALS['CONF']['mail'];
262     $subject = '[' . $NAME . '] Prueba ';
263     if ($tipo == R_OK) $estado = 'OK';
264     else               $estado = 'ERROR';
265     $subject .= $estado;
266     $body .= "Estado: $estado\n";
267     if ($mensaje) $body .= "\n$mensaje\n";
268     if ($intento) $body .= "\n" . $intento->__toString() . "\n";
269     logs("Envío de mail '$subject' a '$to'\n$body\n", DEBUG);
270     $headers = <<<EOT
271 From: {$mconf['from']}
272 Reply-To: {$mconf['admin']}
273 X-Mailer: $NAME $VERSION
274 X-Priority: 5
275 EOT;
276     mail($to, $subject, $body, $headers);
277     //mail($mconf['admin'], $subject, $body, $headers);
278     return true;
279 }
280
281 /**
282  * Cambia permisos, dueño y grupo a un archivo, devuelve string con error o
283  * false si no hay error.
284  */
285 function chmod_own_grp($file, $mod, $own, $grp) {
286     if (!@chmod($file, $mod)) return "Error al cambiar permisos [$mod] a '$file'";
287     if (!@chown($file, $own)) return "Error al cambiar dueño [$own] a '$file'";
288     if (!@chgrp($file, $grp)) return "Error al cambiar grupo [$grp] a '$file'";
289     return false;
290 }
291
292 function enviar_respuesta_error_log($to, $msg = '', $intento = null) {
293     logserr($msg);
294     enviar_respuesta(R_ERR, $to, "ERROR: $msg\n\nSe envió un mail al administrador para revisar el problema.\n", $intento);
295     enviar_respuesta(R_ERR, $GLOBALS['CONF']['mail']['admin'], $msg, $intento);
296 }
297
298 ?>