#!/usr/bin/php proximo_a_probar())) { logs('No hay intento para probar', L_DBG); sleep($gconf['intervalo']); continue; } logs('Nuevo intento a probar (' . $intento->to_line() . ')'); $intento_dir = "{$gconf['data_dir']}/" . $intento->path(); $entrega_dir = "{$gconf['data_dir']}/" . $intento->base_path('entregas'); if (is_readable("$entrega_dir/Makefile")) { $makefile = "$entrega_dir/Makefile"; //XXX @copy("$entrega_dir/Makefile", "$intento_dir/Makefile") // or logserr("Error al copiar Makefile (de '$entrega_dir/Makefile' a '$intento_dir/Makefile'"); // Fallback } elseif (is_readable("{$gconf['data_dir']}/Makefile")) { $makefile = "{$gconf['data_dir']}/Makefile"; //XXX if (!@copy("{$gconf['data_dir']}/Makefile", "$intento_dir/Makefile")) { } else { enviar_respuesta_error_log($mail, 'No hay un Makefile disponible', $intento); continue; } $currdir = getcwd(); if (!@chdir($intento_dir)) { enviar_respuesta_error_log($mail, 'Error al cambiar al directorio del tp', $intento); continue; } logs("Cambio de directorio '$currdir' -> '$intento_dir'", L_DBG); logs('Ejecutando el comando: make -f '.escapeshellarg($makefile), L_DBG); if (exec_get_info('make -f '.escapeshellarg($makefile), $ret, $err, $out)) { if ($ret) { logs('Error al compilar'); logs("Código de retorno: $ret, mensaje: $err)", L_DBG); //XXX $intento->informar_compilacion(false); enviar_respuesta(R_ERR, $mail, "ERROR AL COMPILAR!\n\n$err\n\nCódigo de retorno: $ret\n", $intento); continue; } else { logs('Compilado OK'); //XXX $intento->informar_compilacion(true); // TODO mail acumulativo // enviar_respuesta(R_OK, $mail, 'El intento fue compilado con éxito!', $intento); } } else { //XXX $intento->informar_compilacion(false); enviar_respuesta_error_log($mail, 'No se pudo ejecutar make', $intento); continue; } if (!@mkdir('chroot')) { enviar_respuesta_error_log($mail, 'Error al crear directorio para chroot', $intento); continue; } if (!@rename('tp', 'chroot/tp')) { enviar_respuesta_error_log($mail, 'Error al mover el tp al chroot', $intento); continue; } if (!@copy("{$gconf['data_dir']}/redir", 'chroot/redir')) { enviar_respuesta_error_log($mail, 'Error al copiar redireccionador al chroot', $intento); continue; } if ($err = chmod_own_grp('chroot', 02770, $UID, $GID)) { enviar_respuesta_error_log($mail, $err, $intento); continue; } if ($err = chmod_own_grp('chroot/tp', 0550, $UID, $GID)) { enviar_respuesta_error_log($mail, $err, $intento); continue; } if ($err = chmod_own_grp('chroot/redir', 0550, 0, 0)) { enviar_respuesta_error_log($mail, $err, $intento); continue; } // comienza la ejecución de casos de prueba while ($prueba = $intento->pedir_caso_de_prueba()) { logs('Prueba: ' . $prueba->to_line(), L_DBG); // ejecuta con fork $pid = pcntl_fork(); if ($pid == -1) { // Error al forkear $intento->resultado_de_prueba($prueba, false, 'Error al forkear proceso'); enviar_respuesta_error_log($mail, 'Error al forkear proceso', $intento); continue 2; } elseif ($pid) { // Estamos en el padre, controlamos el tiempo. logs("En el padre (hijo: $pid)", L_DBG); // TODO controlar tiempo. pcntl_waitpid($pid, $exitcode); logs("Fin de ejecución de caso de prueba (hijo: $pid, ret: $exitcode)", L_DBG); $stderr = false; // FIXME ver si salida de error es vacia. if ($exitcode or $stderr) { logs('Comando ejecutado ERROR', L_DBG); if ($ret) { $msg = "El programa salió con código de error $ret"; $msgs[] = $msg; logs($msg, L_DBG); } if ($stderr) { $msg = "El programa imprimió mensajes en la salida de error: '$stderr'"; $msgs[] = $msg; logs($msg, L_DBG); } $msg = join("\n", $msgs); $intento->resultado_de_prueba($prueba, false, $msg); enviar_respuesta(R_ERR, $mail, $msg, $intento); exit(0); // salgo ok del hijo } else { // Sin errores en la ejecución. logs('Comando ejecutado OK', L_DBG); // TODO $salidas = array(); foreach ($prueba->salidas as $salida) { // TODO hacer diffs. if ($salida == 'stdout') { $salidas[$salida] = $stdout; } else { $salidas[$salida] = file_get_contents($salida); } } logs('Salidas: ' . var_export($salidas, true), L_DBG); $intento->resultado_de_prueba($prueba, true, 'Salidas: ' . var_export($salidas, true)); enviar_respuesta(R_ERR, $mail, 'Salidas: ' . var_export($salidas, true), $intento); exit(0); // salgo ok del hijo } } else { // Estamos en el hijo, corremos en chroot. logs('En el hijo', L_DBG); // Hago chroot. if (!@chroot('chroot')) { $intento->resultado_de_prueba($prueba, false, 'Error al hacer chroot'); enviar_respuesta_error_log($mail, 'Error al hacer chroot', $intento); exit(1); // salgo del hijo } logs('Chrooteado, cwd = '.getcwd(), L_DBG); if (!@posix_setuid($usrinfo['uid'])) { $intento->resultado_de_prueba($prueba, false, "Error al cambiar al uid '{$usrinfo['uid']}'"); enviar_respuesta_error_log($mail, "Error al cambiar al uid '{$usrinfo['uid']}'", $intento); exit(2); // salgo del hijo } // TODO poner stdout y stderr en archivos para hacer diff o hacer diffs en memoria $stdout = null; if (in_array('stdout', $prueba->salidas)) $stdout = ''; logs('Se ejecutará: chroot chroot /tp ' . escapeshellarg($prueba->params), L_DBG); if (!pcntl_exec('/redir', array('tp', 'stdin', 'stdout', 'stderr', '/tp', '0', $prueba->params))) { logserr('No se pudo ejecutar el comando'); $intento->resultado_de_prueba($prueba, false, 'No se pudo ejecutar el tp'); enviar_respuesta_error_log($mail, 'No se pudo ejecutar el tp', $intento); exit(4); // salgo del hijo } exit(100); // salgo del hijo (no deberia llegar nunca aca). } break; // FIXME } // TODO make clean } /** * Ejecuta un comando devolviendo el código de error y las salidas. * * @param cmd Comando a ejecutar. * @param ret Código de retorno. * @param err Salida de error (se obtiene sólo si es != null). * @param out Salida estándar (se obtiene sólo si es != null). * @param in Entrada a enviarle al comando (null si no se le envía nada). * @return true si se ejecutó el comando, false si no. */ function exec_fds($cmd, &$ret, $in = false, $out = false, $in = false) { $descriptors = array(); if ($in) $descriptors[0] = get_fd_type($in, 'r'); if ($out) $descriptors[1] = get_fd_type($out, 'w'); if ($err) $descriptors[2] = get_fd_type($err, 'w'); var_dump($descriptors); $proc = proc_open($cmd, $descriptors, $pipes); if (is_resource($proc)) { $ret = proc_close($proc); return true; } return false; } /** * Obtiene un tipo de descriptor para proc_open(). * * @param fd Tipo de descriptor: si es true, se usa un pipe, si es un array * se usa el array y si es otra cosa se usa un archivo con este * nombre. * @param mode Modo de apertura, sólo se usa si es true o string. * @return Array a pasar a proc_open(). */ function get_fd_type($fd, $mode = false) { if ($fd === true) { return array('pipe', $mode); } elseif (is_array($fd)) { return $fd; } return array('file', $fd, $mode); } /** * Ejecuta un comando devolviendo el código de error y las salidas. * * @param cmd Comando a ejecutar. * @param ret Código de retorno. * @param err Salida de error (se obtiene sólo si es != null). * @param out Salida estándar (se obtiene sólo si es != null). * @param in Entrada a enviarle al comando (null si no se le envía nada). * @return true si se ejecutó el comando, false si no. */ function exec_get_info($cmd, &$ret, &$err, &$out, $in = null) { $descriptors = array(array('pipe', 'r'), array('pipe', 'w'), array('pipe', 'w')); $proc = proc_open($cmd, $descriptors, $pipes); if (is_resource($proc)) { if (is_null($in)) { fputs($pipes[0], $in); } if (is_null($out)) { $out = stream_get_contents($pipes[1]); } if (is_null($err)) { $err = stream_get_contents($pipes[2]); } foreach (array(0,1,2) as $i) { fclose($pipes[$i]); } $ret = proc_close($proc); return true; } return false; } function enviar_respuesta($tipo, $to, $mensaje = '', $intento = null) { $mconf = $GLOBALS['CONF']['mail']; $subject = $mconf['prefijo'] . ' Prueba '; if ($tipo == R_OK) $estado = 'OK'; else $estado = 'ERROR'; $subject .= $estado; $body .= "Estado: $estado\n"; if ($mensaje) $body .= "\n$mensaje\n"; if ($intento) $body .= "\n" . $intento->__toString() . "\n"; logs("Envío de mail '$subject' a '$to'\n$body\n", L_DBG); $headers = <<