2 <?php // vim: set binary noeol et sw=4 sts=4:
4 require_once 'T/general.php';
11 $gconf = $CONF['general'];
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
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
25 $intento = new T_Intento;
29 if (!($mail = $intento->proximo_a_probar())) {
30 logs('No hay intento para probar', DEBUG);
31 sleep($gconf['intervalo']);
34 logs('Nuevo intento a probar (' . $intento->to_line() . ')');
36 $intento_dir = "{$gconf['data_dir']}/" . $intento->path();
37 $entrega_dir = "{$gconf['data_dir']}/" . $intento->base_path('entregas');
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'");
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")) {
48 enviar_respuesta_error_log($mail, 'No hay un Makefile disponible', $intento);
53 if (!@chdir($intento_dir)) {
54 enviar_respuesta_error_log($mail, 'Error al cambiar al directorio del tp', $intento);
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)) {
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);
68 //XXX $intento->informar_compilacion(true);
69 // TODO mail acumulativo
70 // enviar_respuesta(R_OK, $mail, 'El intento fue compilado con éxito!', $intento);
73 //XXX $intento->informar_compilacion(false);
74 enviar_respuesta_error_log($mail, 'No se pudo ejecutar make', $intento);
77 if (!@mkdir('chroot')) {
78 enviar_respuesta_error_log($mail, 'Error al crear directorio para chroot', $intento);
81 if (!@rename('tp', 'chroot/tp')) {
82 enviar_respuesta_error_log($mail, 'Error al mover el tp al chroot', $intento);
85 if (!@copy("{$gconf['data_dir']}/redir", 'chroot/redir')) {
86 enviar_respuesta_error_log($mail, 'Error al copiar redireccionador al chroot', $intento);
89 if ($err = chmod_own_grp('chroot', 02770, $UID, $GID)) {
90 enviar_respuesta_error_log($mail, $err, $intento);
93 if ($err = chmod_own_grp('chroot/tp', 0550, $UID, $GID)) {
94 enviar_respuesta_error_log($mail, $err, $intento);
97 if ($err = chmod_own_grp('chroot/redir', 0550, 0, 0)) {
98 enviar_respuesta_error_log($mail, $err, $intento);
102 // comienza la ejecución de casos de prueba
103 while ($prueba = $intento->pedir_caso_de_prueba()) {
104 logs('Prueba: ' . $prueba->to_line(), DEBUG);
109 $intento->resultado_de_prueba($prueba, false, 'Error al forkear proceso');
110 enviar_respuesta_error_log($mail, 'Error al forkear proceso', $intento);
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);
122 $msg = "El programa salió con código de error $ret";
127 $msg = "El programa imprimió mensajes en la salida de error: '$stderr'";
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);
139 foreach ($prueba->salidas as $salida) {
141 if ($salida == 'stdout') {
142 $salidas[$salida] = $stdout;
144 $salidas[$salida] = file_get_contents($salida);
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
153 // Estamos en el hijo, corremos en chroot.
154 logs('En el hijo', DEBUG);
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
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
167 // TODO poner stdout y stderr en archivos para hacer diff o hacer diffs en memoria
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
177 exit(100); // salgo del hijo (no deberia llegar nunca aca).
187 * Ejecuta un comando devolviendo el código de error y las salidas.
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.
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);
211 * Obtiene un tipo de descriptor para proc_open().
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
216 * @param mode Modo de apertura, sólo se usa si es true o string.
217 * @return Array a pasar a proc_open().
219 function get_fd_type($fd, $mode = false) {
221 return array('pipe', $mode);
222 } elseif (is_array($fd)) {
225 return array('file', $fd, $mode);
229 * Ejecuta un comando devolviendo el código de error y las salidas.
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.
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)) {
243 fputs($pipes[0], $in);
246 $out = stream_get_contents($pipes[1]);
249 $err = stream_get_contents($pipes[2]);
251 foreach (array(0,1,2) as $i) {
254 $ret = proc_close($proc);
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';
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);
271 From: {$mconf['from']}
272 Reply-To: {$mconf['admin']}
273 X-Mailer: $NAME $VERSION
276 mail($to, $subject, $body, $headers);
277 //mail($mconf['admin'], $subject, $body, $headers);
282 * Cambia permisos, dueño y grupo a un archivo, devuelve string con error o
283 * false si no hay error.
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'";
292 function enviar_respuesta_error_log($to, $msg = '', $intento = null) {
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);