package freenet.support.io;
import static java.util.concurrent.TimeUnit.MINUTES;
import java.io.File;
import java.io.IOException;
import java.util.Random;
import org.tanukisoftware.wrapper.WrapperManager;
import freenet.support.Fields;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.TimeUtil;
import freenet.support.Logger.LogLevel;
/** Tracks the current temporary files settings (dir and prefix), and translates between ID's and
* filenames. Also provides functions for creating tempfiles (which should be safe against symlink
* attacks and race conditions). FIXME Consider using File.createTempFile(). Note that using our
* own code could actually be more secure if we use a better PRNG than they do (they use
* "new Random()" IIRC, but maybe that's fixed now?). If we do change to using
* File.createTempFile(), we will need to change TempFileBucket accordingly.
* @author toad
*/
public class FilenameGenerator {
private transient Random random;
private String prefix;
private File tmpDir;
private static volatile boolean logMINOR;
static {
Logger.registerLogThresholdCallback(new LogThresholdCallback() {
@Override
public void shouldUpdate() {
logMINOR = Logger.shouldLog(LogLevel.MINOR, this);
}
});
}
/**
* @param random
* @param wipeFiles
* @param dir if <code>null</code> then use the default temporary directory
* @param prefix
* @throws IOException
*/
public FilenameGenerator(Random random, boolean wipeFiles, File dir, String prefix) throws IOException {
this.random = random;
this.prefix = prefix;
if (dir == null)
tmpDir = FileUtil.getCanonicalFile(new File(System.getProperty("java.io.tmpdir")));
else
tmpDir = FileUtil.getCanonicalFile(dir);
if(!tmpDir.exists()) {
tmpDir.mkdir();
}
if(!(tmpDir.isDirectory() && tmpDir.canRead() && tmpDir.canWrite()))
throw new IOException("Not a directory or cannot read/write: "+tmpDir);
if(wipeFiles) {
long wipedFiles = 0;
long wipeableFiles = 0;
long startWipe = System.currentTimeMillis();
File[] filenames = tmpDir.listFiles();
if(filenames != null) {
for(int i=0;i<filenames.length;i++) {
WrapperManager.signalStarting((int) MINUTES.toMillis(5));
if(i % 1024 == 0 && i > 0)
// User may want some feedback during startup
System.err.println("Deleted "+wipedFiles+" temp files ("+(i - wipeableFiles)+" non-temp files in temp dir)");
File f = filenames[i];
String name = f.getName();
if((((File.separatorChar == '\\') && name.toLowerCase().startsWith(prefix.toLowerCase())) ||
name.startsWith(prefix))) {
wipeableFiles++;
if((!f.delete()) && f.exists())
System.err.println("Unable to delete temporary file "+f+" - permissions problem?");
else
wipedFiles++;
}
}
long endWipe = System.currentTimeMillis();
System.err.println("Deleted "+wipedFiles+" of "+wipeableFiles+" temporary files ("+(filenames.length-wipeableFiles)+" non-temp files in temp directory) in "+TimeUtil.formatTime(endWipe-startWipe));
}
}
}
public long makeRandomFilename() throws IOException {
long randomFilename; // should be plenty
while(true) {
randomFilename = random.nextLong();
if(randomFilename == -1) continue; // Disallowed as used for error reporting
String filename = prefix + Long.toHexString(randomFilename);
File ret = new File(tmpDir, filename);
if(ret.createNewFile()) {
if(logMINOR)
Logger.minor(this, "Made random filename: "+ret, new Exception("debug"));
return randomFilename;
}
}
}
public File getFilename(long id) {
return new File(tmpDir, prefix + Long.toHexString(id));
}
public File makeRandomFile() throws IOException {
return getFilename(makeRandomFilename());
}
public File getDir() {
return tmpDir;
}
protected boolean matches(File file) {
return FileUtil.equals(file.getParentFile(), tmpDir) &&
file.getName().startsWith(prefix);
}
public File maybeMove(File file, long id) {
if(matches(file)) return file;
File newFile = getFilename(id);
Logger.normal(this, "Moving tempfile "+file+" to "+newFile);
if(FileUtil.moveTo(file, newFile, false))
return newFile;
else {
Logger.error(this, "Unable to move old temporary file "+file+" to "+newFile);
return file;
}
}
}