package org.kit.furia.fragment;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.ajmm.obsearch.asserts.OBAsserts;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.kit.furia.misc.FuriaProperties;
/*
Furia-chan: An Open Source software license violation detector.
Copyright (C) 2007 Kyushu Institute of Technology
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program 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 the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/**
* FragmentBuilderClient is in charge of executing soot in a set of java class
* directories and leaving a "fragments" file in the specified output directory.
* Since Soot has problems when executed several times during the life of a VM
* (reset had issues) we call a new VM for each program that will be fragmented.
* @author Arnoldo Jose Muller Molina
* @since 0
*/
public class FragmentBuilderClient {
/**
* Used to stop the threads if an error occurs.
*/
private Exception exception = null;
/**
* Logger.
*/
private static Logger logger = Logger.getLogger("FragmentBuilderClient");
/**
* Files that will be processed.
*/
private File[] filesToProcess;
/**
* Index used to access filesToProcess.
*/
private AtomicInteger i;
/**
* Used to wait until all threads have finished.
*/
private CountDownLatch join;
/**
* Maximum # of expansions performed for each fragment.
*/
private int maxExpansions;
/**
* If the program will receive one directory (that holds in its root a set
* of java class files) (false) or if the directory received contains
* directories that contain class files. Each of these directories is
* treated as a different application.
*/
private boolean directoryOfDirectoriesMode;
/**
* The output directory to use.
*/
protected File outputDirectory;
/**
* Fail if there is an error.
*/
private boolean failOnError;
/**
* Number of cpus.
*/
private int cpus;
/**
* Timeout for subprocesses.
*/
private long timeout;
/**
* The frequency we will check each process to complete.
*/
private static final long POLL_INTERVAL = 1000 * 5; // every seconds.
/**
* Error code returned by the caller if there is a timeout exception.
*/
private static final int TIMEOUT_ERRORCODE = -20; // every seconds.
/**
* Fragment engine to employ.
*/
private String engine;
/**
* Takes a directory of a set of directories and generates fragments out of
* the given folders. An infinite timeout is set. (Not recommended for Soot)
* @param cpus
* The number of CPUS to employ
* @param directory
* The directory that will be opened.
* @param directoryOfDirectoriesMode
* If the program will receive one directory (that holds in
* its root a set of java class files) (false) or if the
* directory received contains directories that contain class
* files. Each of these directories is treated as a different
* application.
* @param outputDirectory
* The directory that holds the resulting files from the
* operation. If directoryOfDirectoriesMode == false then the
* output data files will be copied directory to
* outputDirectory. Otherwise a directory outputDirectory/
* will be created for each application where is the
* application name.
* @param engine Fragment engine that will be used 'soot' or 'asm'.
* @param failOnError
* If true, stops if there is an error.
*/
public FragmentBuilderClient(boolean directoryOfDirectoriesMode,
File directory, int cpus, File outputDirectory, boolean failOnError, String engine)
throws Exception, InterruptedException {
this(directoryOfDirectoriesMode, directory, cpus, outputDirectory,
failOnError, (long) 0, engine);
}
/**
* Takes a directory of a set of directories and generates fragments out of
* the given folders.
* @param cpus
* The number of CPUS to employ
* @param directory
* The directory that will be opened.
* @param directoryOfDirectoriesMode
* If the program will receive one directory (that holds in
* its root a set of java class files) (false) or if the
* directory received contains directories that contain class
* files. Each of these directories is treated as a different
* application.
* @param outputDirectory
* The directory that holds the resulting files from the
* operation. If directoryOfDirectoriesMode == false then the
* output data files will be copied directory to
* outputDirectory. Otherwise a directory outputDirectory/
* will be created for each application where is the
* application name.
* @param failOnError
* If true, stops if there is an error.
* @param fragmentEngine The fragment engine to be used, currently soot or ASM.
* @param timeout
* Amount of time in milliseconds to wait until one program
* is fragmented.
*/
public FragmentBuilderClient(boolean directoryOfDirectoriesMode,
File directory, int cpus, File outputDirectory,
boolean failOnError, long timeout, String fragmentEngine) throws Exception,
InterruptedException {
if(! ( fragmentEngine.equals("asm") || fragmentEngine.equals("soot"))){
throw new IllegalArgumentException("Engines available are 'asm' or 'soot'.");
}
this.engine = fragmentEngine;
this.timeout = timeout;
this.cpus = cpus;
if (directoryOfDirectoriesMode) {
filesToProcess = directory.listFiles();
} else {
filesToProcess = new File[1];
filesToProcess[0] = directory;
}
this.directoryOfDirectoriesMode = directoryOfDirectoriesMode;
this.outputDirectory = outputDirectory;
this.i = new AtomicInteger(0);
join = new CountDownLatch(filesToProcess.length);
this.failOnError = failOnError;
int i = 0;
while (i < cpus) {
new Thread(new FragmentExecutor()).start();
i++;
}
boolean interrupted = true;
// wait for all the threads to complete
while (interrupted) {
try {
join.await();
interrupted = false;
} catch (InterruptedException e) {
// dancing all alone...
}
}
if (exception != null) {
throw exception;
}
}
/**
* Executes a new jvm on each folder and creates the fragments
* @author Arnoldo Jose Muller Molina
*/
private class FragmentExecutor implements Runnable {
public void run() {
try {
while (exception == null) {
int cx = i.getAndIncrement();
if (cx < filesToProcess.length) {
File dirToProcess = filesToProcess[cx];
logger.info("Processing: " + (cx + 1) + " of "
+ filesToProcess.length + " % " + dirToProcess
+ " count: " + join.getCount());
execApp(dirToProcess);
join.countDown();
// if there is an error in the execution, we expect
} else {
break;
}
}
if (exception != null) {
logger
.fatal("Quitting thread because exception was not null "
+ exception.toString());
while (join.getCount() > 0) {
join.countDown();
}
}
logger.debug("Quitting thread");
} catch (Exception e) {
logger.fatal("Caught Exception", e);
}
}
/**
* Calls a sub-process that will fragment
* @param dirToProcess
*/
private void execApp(File dirToProcess) {
File appOutputDir;
// fixed the proper output directory
if (directoryOfDirectoriesMode) {
appOutputDir = new File(outputDirectory, dirToProcess.getName());
} else {
appOutputDir = outputDirectory;
}
List < String > command = new LinkedList < String >();
command.add("java");
long ramToAllocate = Runtime.getRuntime().maxMemory() / 1024 / 1024
;
command.add("-Xmx" + (ramToAllocate) + "m");
command.add(FragmentBuilderClientAux.class.getCanonicalName());
command.add(dirToProcess.toString());
command.add(appOutputDir.toString());
command.add(engine);
try {
// create dir if it is not created
if (!outputDirectory.exists()) {
outputDirectory.mkdirs();
}
OBAsserts.chkAssert(outputDirectory.exists(), "Directory "
+ outputDirectory + " was not created succesfully");
// add the current directory as part of the classpath.
ProcessBuilder pb = new ProcessBuilder(command);
pb.directory(new File(System.getProperty("user.dir")));
Map < String, String > env = pb.environment();
env.put("CLASSPATH", System.getProperty("java.class.path"));
env
.put("log4j.file", FuriaProperties
.getProperty("log4j.file"));
pb.redirectErrorStream(true);
long endTime = System.currentTimeMillis() + timeout;
Process p = pb.start();
boolean interrupted = true;
// while (interrupted) {
StringBuilder x = null;
try {
InputStream in = p.getInputStream();
InputStreamReader inR = new InputStreamReader(in);
BufferedReader bIn = new BufferedReader(inR);
int res = TIMEOUT_ERRORCODE; // code for timeout
if (timeout == 0) {
res = p.waitFor();
} else {
boolean finished = false;
Object lock = new Object();
while (!finished) {
try {
res = p.exitValue();
finished = true;
} catch (IllegalThreadStateException notFinished) {
try {
synchronized (lock) {
lock.wait(POLL_INTERVAL);
}
} catch (InterruptedException waitInterrupted) {
// nothing to do.
}
if (endTime < System.currentTimeMillis()) {
// we have to stop
finished = true;
res = TIMEOUT_ERRORCODE; // :)
}
}
}
}
logger.info("Completed app:" + dirToProcess.toString()
+ " count " + join.getCount());
if (res != 0) {
// Read the output of the invoked program and
// print it to string.
String line = bIn.readLine();
x = new StringBuilder();
while (line != null) {
x.append(line + "\n");
line = bIn.readLine();
}
bIn.close();
FileWriter result = new FileWriter(new File(
appOutputDir, "fatal.txt"));
// add the msg timeout to the result.
if (res == TIMEOUT_ERRORCODE) {
x.append("\n Timeout ! :(\n ");
}
result.write(x.toString());
result.close();
if (failOnError) {
throw new Exception(
"Failed while executing command:"
+ command.toString()
+ " returned code: " + res + "\n"
+ x.toString());
} else {
if(res == 7){
logger.warn("No clases found for: "
+ dirToProcess
);
}else{
logger.warn("Failed to process: "
+ dirToProcess
+ " check "
+ (new File(appOutputDir, "fatal.txt"))
.toString());
}
}
}
interrupted = false;
} catch (InterruptedException e) {
logger.fatal("Process interrupted");
} catch (FileNotFoundException e2) { // we could not even
// create the fatal.txt
// msg.
if (x != null) {
throw new Exception("Failed while executing command:"
+ command.toString() + "\n" + "\n"
+ x.toString());// + "\n Passed env:\n" +
// pb.environment().toString()
// + " \nworking dir: " +
// pb.directory());
}
}
// }
} catch (Exception e) {
logger.fatal("Error while executing command", e);
exception = e;
}
}
}
}