/* * AiExecutor.java * * Copyright (C) 2015 Pixelgaffer * * This work is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2 of the License, or any later * version. * * This work is distributed in the hope that it will be useful, but without * any warranty; without even the implied warranty of merchantability or * fitness for a particular purpose. See version 2 and version 3 of the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.pixelgaffer.turnierserver.sandboxmanager; import static java.lang.Math.min; import static org.pixelgaffer.turnierserver.FileOwnerChanger.changeOwner; import static org.pixelgaffer.turnierserver.PropertyUtils.getString; import static org.pixelgaffer.turnierserver.PropertyUtils.getStringRequired; import static org.pixelgaffer.turnierserver.sandboxmanager.SandboxMain.commands; import static org.pixelgaffer.turnierserver.sandboxmanager.SandboxMain.etc; import java.io.BufferedReader; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.lang.ProcessBuilder.Redirect; import java.security.NoSuchAlgorithmException; import java.util.LinkedList; import java.util.List; import java.util.Properties; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; import org.pixelgaffer.turnierserver.Airbrake; import lombok.Getter; public class AiExecutor implements Runnable { @SuppressWarnings("serial") public class AiStartException extends Exception { public AiStartException () { super(); } public AiStartException (String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } public AiStartException (String message, Throwable cause) { super(message, cause); } public AiStartException (String message) { super(message); } public AiStartException (Throwable cause) { super(cause); } } @Getter private Job job; @Getter private JobControl jobControl; private String boxdir; private File dir, binArchive, binDir, aiProp; private Properties start; private Process proc; public AiExecutor (Job job, JobControl ctrl) throws AiStartException { this.job = job; jobControl = ctrl; // isolate initialisieren try { ProcessBuilder pb = new ProcessBuilder(getString("isolate.bin", "isolate"), "--cg", "--init", "-b", Integer.toString(job.getBoxid())); pb.redirectError(Redirect.INHERIT); System.out.println("$ " + pb.command()); Process p = pb.start(); BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream())); boxdir = in.readLine(); if (p.waitFor() != 0) throw new AiStartException("Error while initialising isolate (exit code: " + p.exitValue() + ")"); dir = new File(boxdir, "box"); dir.mkdirs(); } catch (AiStartException e) { throw e; } catch (Exception e) { throw new AiStartException(e); } } @Override public void run () { try { // if (getJob().getId() < 0) // throw new AiStartException("Simulierter Crash"); download(); generateProps(); executeAi(); } catch (Throwable e) { Airbrake.log(e).printStackTrace(); jobControl.jobFinished(getJob().getUuid()); SandboxMain.getClient().sendMessage(getJob().getUuid(), 'T'); } } private static void makeReadOnly (File f, boolean executable) throws IOException { changeOwner(f, "root"); f.setExecutable(executable); f.setReadable(true); f.setWritable(true, true); } private static void extract (File f, File d) throws IOException { System.out.println("extract(" + f + ", " + d + ")"); TarArchiveInputStream in = new TarArchiveInputStream(new BZip2CompressorInputStream(new FileInputStream(f))); TarArchiveEntry entry; while ((entry = in.getNextTarEntry()) != null) { if (entry.isDirectory()) new File(d, entry.getName()).mkdir(); else { File file = new File(d, entry.getName()); System.out.println(file.getAbsolutePath()); OutputStream out = new FileOutputStream(file); long size = entry.getSize(); long read = 0; while (size > read) { int toRead = (int)min(size - read, Integer.MAX_VALUE); while (toRead > 0) { byte buf[] = new byte[min(toRead, 8192)]; int r = in.read(buf); if (r <= 0) { out.close(); in.close(); throw new EOFException(); } toRead -= r; read += r; out.write(buf, 0, r); } } out.close(); } } in.close(); } protected void download () throws NoSuchAlgorithmException, IOException { binArchive = new File(dir, "bin.tar.bz2"); MirrorClient.retrieveAi(getJob().getId(), getJob().getVersion(), binArchive.getAbsolutePath()); makeReadOnly(binArchive, false); binDir = new File(dir, "bin"); binDir.mkdir(); extract(binArchive, binDir); start = new Properties(); start.load(new FileInputStream(new File(binDir, "start.prop"))); int libs = Integer.parseInt(start.getProperty("libraries.size")); SandboxMain.getLogger().debug("Libraries: " + libs); for (int i = 0; i < libs; i++) { File path = new File(binDir, start.getProperty("libraries." + i + ".path")); path.mkdirs(); MirrorClient.retrieveLib(getJob().getLang(), start.getProperty("libraries." + i + ".name"), new File(path, ".tar.bz2").getAbsolutePath()); makeReadOnly(new File(path, ".tar.bz2"), false); extract(new File(path, ".tar.bz2"), path); } } protected void generateProps () throws IOException { aiProp = new File(dir, "ai.prop"); Properties aiProps = new Properties(); aiProps.put("turnierserver.worker.host", getStringRequired("worker.host")); aiProps.put("turnierserver.worker.server.port", getStringRequired("worker.port")); aiProps.put("turnierserver.worker.server.aichar", "A"); aiProps.put("turnierserver.ai.uuid", getJob().getUuid().toString()); aiProps.put("turnierserver.debug", getString("turnierserver.debug", "false")); aiProps.store(new FileOutputStream(aiProp), "GENERATED FILE - DO NOT EDIT"); makeReadOnly(aiProp, false); } protected void executeAi () throws IOException { List<String> cmd = new LinkedList<>(); cmd.add(getString("isolate.bin", "isolate")); String command; if (commands.get(getJob().getLang()).isEmpty()) command = start.getProperty("command"); else command = commands.get(getJob().getLang()); if (command.startsWith(".")) { SandboxMain.getLogger() .debug("Flagging " + new File(binDir, command.substring(2)).getAbsolutePath() + " as executable"); new File(binDir, command.substring(2)).setExecutable(true); command = "/box/bin" + command.substring(1); } cmd.add("--cg"); cmd.add("-p"); cmd.add("--share-net"); cmd.add("-q"); cmd.add("0,0"); cmd.add("--time=" + job.getTimeout()); cmd.add("--wall-time=" + job.getTimeout()); cmd.add("--dir=/etc/=" + etc.getAbsolutePath()); cmd.add("--dir=/usr/lib/jvm/"); cmd.add("-c"); cmd.add("/box/bin/"); cmd.add("--env=LANG"); for (int i = 0; i < Integer.parseInt(start.getProperty("environment.size")); i++) { cmd.add("--env=" + start.getProperty("environment." + i + ".key") + "=" + start.getProperty("environment." + i + ".value")); } cmd.add("--run"); cmd.add("-b"); cmd.add(Integer.toString(job.getBoxid())); cmd.add("--"); cmd.add(command); for (int i = 0; i < Integer.parseInt(start.getProperty("arguments.size")); i++) { cmd.add(start.getProperty("arguments." + i)); } cmd.add("/box/" + aiProp.getName()); SandboxMain.getLogger().debug("Der Befehl ist " + cmd); ProcessBuilder pb = new ProcessBuilder(cmd); pb.redirectErrorStream(true); pb.redirectOutput(Redirect.INHERIT); proc = pb.start(); SandboxMain.getClient().sendMessage(getJob().getUuid(), 'S'); new Thread( () -> { int ret; try { ret = proc.waitFor(); SandboxMain.getLogger().debug("Die KI hat sich mit dem Statuscode " + ret + " beendet"); SandboxMain.getClient().sendMessage(getJob().getUuid(), 'F'); ProcessBuilder pb0 = new ProcessBuilder(getString("isolate.bin", "isolate"), "--cleanup", "-b", Integer.toString(job.getBoxid())); SandboxMain.getLogger().debug(pb0.command()); pb0.redirectErrorStream(true); pb0.redirectOutput(Redirect.INHERIT); if (pb0.start().waitFor() != 0) SandboxMain.getLogger().critical("Fehler beim Aufräumen von isolate"); jobControl.jobFinished(getJob().getUuid()); } catch (Exception e) { Airbrake.log(e).printStackTrace(); } } , "IsolateCleanup").start(); } public void terminateAi () { SandboxMain.getLogger().info("terminiere " + getJob()); proc.destroy(); SandboxMain.getClient().sendMessage(getJob().getUuid(), 'T'); } public void killAi () { SandboxMain.getLogger().info("töte " + getJob()); proc.destroy(); SandboxMain.getClient().sendMessage(getJob().getUuid(), 'K'); } }