Big directory rework.

Eliminate all uses of the current working directory, and
set up multiple directories specified by absolute paths for various uses.

Add a WorkingDir class to create a user config directory and
migrate files to it for new installs.
The directory will be $HOME/.i2p on linux and %APPDIR%\I2P on Windows,
or as specified in the system property -Di2p.dir.config=/path/to/i2pdir
All files except for the base install and temp files will be
in the config directory by default.
Temp files will be in a i2p-xxxxx subdirectory of the system temp directory
specified by the system property java.io.tmpdir.

Convert all file opens in the code to be relative to a specific directory,
as specified in the context. Code and applications should never open
files relative to the current working directory (e.g. new File("foo")).
All files should be accessed in the appropriate context directory,
e.g. new File(_context.getAppDir(), "foo").

The router.config file location may be specified as a system property on the
java command line with -Drouter.configLocation=/path/to/router.config
All directories may be specified as properties in the router.config file.

The migration will copy all files from an existing installation,
except i2psnark/, with the system property -Di2p.dir.migrate=true.
Otherwise it will just set up a new directory with a minimal configuration.

The migration will also create a modified wrapper.config and (on linux only)
a modified i2prouter script, and place them in the config directory.

There are no changes to the installer or the default i2prouter, i2prouter.bat,
i2prouter, wrapper.config, runplain.sh, windows service installer/uninstaller,
etc. in this checkin.


    *  Directories. These are all set at instantiation and will not be changed by
    *  subsequent property changes.
    *  All properties, if set, should be absolute paths.
    *
    *  Name	Property 	Method		Files
    *  -----	-------- 	-----		-----
    *  Base	i2p.dir.base	getBaseDir()	lib/, webapps/, docs/, geoip/, licenses/, ...
    *  Temp	i2p.dir.temp	getTempDir()	Temporary files
    *  Config	i2p.dir.config	getConfigDir()	*.config, hosts.txt, addressbook/, ...
    *
    *  (the following all default to the same as Config)
    *
    *  Router	i2p.dir.router	getRouterDir()	netDb/, peerProfiles/, router.*, keyBackup/, ...
    *  Log	i2p.dir.log	getLogDir()	wrapper.log*, logs/
    *  PID	i2p.dir.pid	getPIDDir()	wrapper *.pid files, router.ping
    *  App	i2p.dir.app	getAppDir()	eepsite/, ...
    *
    *  Note that we can't control where the wrapper actually puts its files.

All these will be set appropriately in a Router Context.
In an I2P App Context, all except Temp will be the current working directory.

Lightly tested so far, needs much more testing.
This commit is contained in:
zzz
2009-06-04 19:14:40 +00:00
parent 8ff2724213
commit 524a25eb2c
46 changed files with 1039 additions and 244 deletions

View File

@ -9,6 +9,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import net.i2p.I2PAppContext;
/**
* @author Iakin
* A class for retrieveing details about the CPU using the CPUID assembly instruction.
@ -503,9 +505,10 @@ public class CPUID {
FileOutputStream fos = null;
try {
InputStream libStream = resource.openStream();
outFile = new File(libPrefix + "jcpuid" + libSuffix);
outFile = new File(I2PAppContext.getGlobalContext().getBaseDir(), libPrefix + "jcpuid" + libSuffix);
fos = new FileOutputStream(outFile);
byte buf[] = new byte[4096*1024];
// wtf this was 4096*1024 which is really excessive for a roughly 4KB file
byte buf[] = new byte[4096];
while (true) {
int read = libStream.read(buf);
if (read < 0) break;

View File

@ -1,5 +1,6 @@
package net.i2p;
import java.io.File;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
@ -20,10 +21,12 @@ import net.i2p.crypto.KeyGenerator;
import net.i2p.crypto.SHA256Generator;
import net.i2p.crypto.SessionKeyManager;
import net.i2p.crypto.TransientSessionKeyManager;
import net.i2p.data.DataHelper;
import net.i2p.data.RoutingKeyGenerator;
import net.i2p.stat.StatManager;
import net.i2p.util.Clock;
import net.i2p.util.ConcurrentHashSet;
import net.i2p.util.FileUtil;
import net.i2p.util.FortunaRandomSource;
import net.i2p.util.KeyRing;
import net.i2p.util.LogManager;
@ -96,6 +99,13 @@ public class I2PAppContext {
private volatile boolean _keyGeneratorInitialized;
protected volatile boolean _keyRingInitialized; // used in RouterContext
private Set<Runnable> _shutdownTasks;
private File _baseDir;
private File _configDir;
private File _routerDir;
private File _pidDir;
private File _logDir;
private File _appDir;
private File _tmpDir;
/**
@ -155,8 +165,148 @@ public class I2PAppContext {
_logManagerInitialized = false;
_keyRingInitialized = false;
_shutdownTasks = new ConcurrentHashSet(0);
initializeDirs();
}
/**
* Directories. These are all set at instantiation and will not be changed by
* subsequent property changes.
* All properties, if set, should be absolute paths.
*
* Name Property Method Files
* ----- -------- ----- -----
* Base i2p.dir.base getBaseDir() lib/, webapps/, docs/, geoip/, licenses/, ...
* Temp i2p.dir.temp getTempDir() Temporary files
* Config i2p.dir.config getConfigDir() *.config, hosts.txt, addressbook/, ...
*
* (the following all default to the same as Config)
*
* Router i2p.dir.router getRouterDir() netDb/, peerProfiles/, router.*, keyBackup/, ...
* Log i2p.dir.log getLogDir() wrapper.log*, logs/
* PID i2p.dir.pid getPIDDir() wrapper *.pid files, router.ping
* App i2p.dir.app getAppDir() eepsite/, ...
*
* Note that we can't control where the wrapper puts its files.
*
* The app dir is where all data files should be. Apps should always read and write files here,
* using a constructor such as:
*
* String path = mypath;
* File f = new File(path);
* if (!f.isAbsolute())
* f = new File(_context.geAppDir(), path);
*
* and never attempt to access files in the CWD using
*
* File f = new File("foo");
*
* An app should assume the CWD is not writable.
*
* Here in I2PAppContext, all the dirs default to CWD.
* However these will be different in RouterContext, as Router.java will set
* the properties in the RouterContext constructor.
*
* Apps should never need to access the base dir, which is the location of the base I2P install.
* However this is provided for the router's use, and for backward compatibility should an app
* need to look there as well.
*
* All dirs except the base are created if they don't exist, but the creation will fail silently.
*/
private void initializeDirs() {
String s = getProperty("i2p.dir.base", System.getProperty("user.dir"));
_baseDir = new File(s);
// config defaults to base
s = getProperty("i2p.dir.config");
if (s != null) {
_configDir = new File(s);
if (!_configDir.exists())
_configDir.mkdir();
} else {
_configDir = _baseDir;
}
_configDir = new File(s);
// router defaults to config
s = getProperty("i2p.dir.router");
if (s != null) {
_routerDir = new File(s);
if (!_routerDir.exists())
_routerDir.mkdir();
} else {
_routerDir = _configDir;
}
// these all default to router
s = getProperty("i2p.dir.pid");
if (s != null) {
_pidDir = new File(s);
if (!_pidDir.exists())
_pidDir.mkdir();
} else {
_pidDir = _routerDir;
}
s = getProperty("i2p.dir.log");
if (s != null) {
_logDir = new File(s);
if (!_logDir.exists())
_logDir.mkdir();
} else {
_logDir = _routerDir;
}
s = getProperty("i2p.dir.app");
if (s != null) {
_appDir = new File(s);
if (!_appDir.exists())
_appDir.mkdir();
} else {
_appDir = _routerDir;
}
// comment these out later, don't want user names in the wrapper logs
System.err.println("Base directory: " + _baseDir.getAbsolutePath());
System.err.println("Config directory: " + _configDir.getAbsolutePath());
System.err.println("Router directory: " + _routerDir.getAbsolutePath());
System.err.println("App directory: " + _appDir.getAbsolutePath());
System.err.println("Log directory: " + _logDir.getAbsolutePath());
System.err.println("PID directory: " + _pidDir.getAbsolutePath());
System.err.println("Temp directory: " + getTempDir().getAbsolutePath());
}
public File getBaseDir() { return _baseDir; }
public File getConfigDir() { return _configDir; }
public File getRouterDir() { return _routerDir; }
public File getPIDDir() { return _pidDir; }
public File getLogDir() { return _logDir; }
public File getAppDir() { return _appDir; }
public File getTempDir() {
// fixme don't synchronize every time
synchronized (this) {
if (_tmpDir == null) {
String d = getProperty("i2p.dir.temp", System.getProperty("java.io.tmpdir"));
// our random() probably isn't warmed up yet
String f = "i2p-" + (new java.util.Random()).nextInt() + ".tmp";
_tmpDir = new File(d, f);
if (_tmpDir.exists()) {
// good or bad ?
} else if (_tmpDir.mkdir()) {
_tmpDir.deleteOnExit();
} else {
System.err.println("Could not create temp dir " + _tmpDir.getAbsolutePath());
_tmpDir = new File(_routerDir, "tmp");
_tmpDir.mkdir();
}
}
}
return _tmpDir;
}
/** don't rely on deleteOnExit() */
public void deleteTempDir() {
synchronized (this) {
if (_tmpDir != null) {
FileUtil.rmdir(_tmpDir, false);
_tmpDir = null;
}
}
}
/**
* Access the configuration attributes of this context, using properties
* provided during the context construction, or falling back on

View File

@ -89,7 +89,7 @@ public class HostsTxtNamingService extends NamingService {
String hostsfile = (String)filenames.get(i);
Properties hosts = new Properties();
try {
File f = new File(hostsfile);
File f = new File(_context.getRouterDir(), hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
DataHelper.loadProps(hosts, f, true);
@ -119,7 +119,7 @@ public class HostsTxtNamingService extends NamingService {
String hostsfile = (String)filenames.get(i);
Properties hosts = new Properties();
try {
File f = new File(hostsfile);
File f = new File(_context.getRouterDir(), hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
DataHelper.loadProps(hosts, f, true);
Set keyset = hosts.keySet();
@ -145,7 +145,7 @@ public class HostsTxtNamingService extends NamingService {
String hostsfile = (String)filenames.get(i);
Properties hosts = new Properties();
try {
File f = new File(hostsfile);
File f = new File(_context.getRouterDir(), hostsfile);
if ( (f.exists()) && (f.canRead()) ) {
DataHelper.loadProps(hosts, f, true);
Set keyset = hosts.keySet();

View File

@ -1,6 +1,7 @@
package net.i2p.crypto;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@ -276,7 +277,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
}
private static final void showVersionCLI(String signedFile) {
String versionString = new TrustedUpdate().getVersionString(signedFile);
String versionString = new TrustedUpdate().getVersionString(new File(signedFile));
if (versionString == "")
System.out.println("No version string found in file '" + signedFile + "'");
@ -294,7 +295,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
}
private static final void verifySigCLI(String signedFile) {
boolean isValidSignature = new TrustedUpdate().verify(signedFile);
boolean isValidSignature = new TrustedUpdate().verify(new File(signedFile));
if (isValidSignature)
System.out.println("Signature VALID");
@ -303,7 +304,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
}
private static final void verifyUpdateCLI(String signedFile) {
boolean isUpdate = new TrustedUpdate().isUpdatedVersion(CoreVersion.VERSION, signedFile);
boolean isUpdate = new TrustedUpdate().isUpdatedVersion(CoreVersion.VERSION, new File(signedFile));
if (isUpdate)
System.out.println("File version is newer than current version.");
@ -347,7 +348,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
* @return The version string read, or an empty string if no version string
* is present.
*/
public String getVersionString(String signedFile) {
public String getVersionString(File signedFile) {
FileInputStream fileInputStream = null;
try {
@ -396,9 +397,9 @@ D8usM7Dxp5yrDrCYZ5AIijc=
* @return <code>true</code> if the signed update file's version is newer
* than the current version, otherwise <code>false</code>.
*/
public boolean isUpdatedVersion(String currentVersion, String signedFile) {
public boolean isUpdatedVersion(String currentVersion, File signedFile) {
_newVersion = getVersionString(signedFile);
return needsUpdate(currentVersion, getVersionString(signedFile));
return needsUpdate(currentVersion, _newVersion);
}
/**
@ -413,7 +414,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
* @return <code>null</code> if the signature and version were valid and the
* data was moved, and an error <code>String</code> otherwise.
*/
public String migrateVerified(String currentVersion, String signedFile, String outputFile) {
public String migrateVerified(String currentVersion, File signedFile, File outputFile) {
if (!isUpdatedVersion(currentVersion, signedFile))
return "Downloaded version is not greater than current version";
@ -606,7 +607,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
* @return <code>true</code> if the file has a valid signature, otherwise
* <code>false</code>.
*/
public boolean verify(String signedFile) {
public boolean verify(File signedFile) {
for (int i = 0; i < _trustedKeys.size(); i++) {
SigningPublicKey signingPublicKey = new SigningPublicKey();
@ -662,7 +663,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
}
}
return verify(signedFile, signingPublicKey);
return verify(new File(signedFile), signingPublicKey);
}
/**
@ -676,7 +677,7 @@ D8usM7Dxp5yrDrCYZ5AIijc=
* @return <code>true</code> if the file has a valid signature, otherwise
* <code>false</code>.
*/
public boolean verify(String signedFile, SigningPublicKey signingPublicKey) {
public boolean verify(File signedFile, SigningPublicKey signingPublicKey) {
FileInputStream fileInputStream = null;
try {

View File

@ -109,6 +109,10 @@ public class PrivateKeyFile {
this(new File(file), I2PClientFactory.createClient());
}
public PrivateKeyFile(File file) {
this(file, I2PClientFactory.createClient());
}
public PrivateKeyFile(File file, I2PClient client) {
this.file = file;
this.client = client;

View File

@ -1,6 +1,7 @@
package net.i2p.stat;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
@ -109,6 +110,9 @@ public class BufferedStatLog implements StatLog {
String filename = _context.getProperty(StatManager.PROP_STAT_FILE);
if (filename == null)
filename = StatManager.DEFAULT_STAT_FILE;
File foo = new File(filename);
if (!foo.isAbsolute())
filename = (new File(_context.getRouterDir(), filename)).getAbsolutePath();
if ( (_outFile != null) && (_outFile.equals(filename)) ) {
// noop
} else {

View File

@ -18,6 +18,12 @@ import java.util.zip.ZipFile;
/**
* General helper methods for messing with files
*
* These are static methods that do NOT convert arguments
* to absolute paths for a particular context and directtory.
*
* Callers should ALWAYS provide absolute paths as arguments,
* and should NEVER assume files are in the current working directory.
*
*/
public class FileUtil {
/**

View File

@ -67,8 +67,8 @@ public class LogManager {
/** when was the config file last read (or -1 if never) */
private long _configLastRead;
/** filename of the config file */
private String _location;
/** the config file */
private File _locationFile;
/** Ordered list of LogRecord elements that have not been written out yet */
private List _records;
/** List of explicit overrides of log levels (LogLimit objects) */
@ -115,11 +115,11 @@ public class LogManager {
_logs = new HashMap(128);
_defaultLimit = Log.ERROR;
_configLastRead = 0;
_location = context.getProperty(CONFIG_LOCATION_PROP, CONFIG_LOCATION_DEFAULT);
_context = context;
_log = getLog(LogManager.class);
String location = context.getProperty(CONFIG_LOCATION_PROP, CONFIG_LOCATION_DEFAULT);
setConfig(location);
_consoleBuffer = new LogConsoleBuffer(context);
loadConfig();
_writer = new LogWriter(this);
Thread t = new I2PThread(_writer);
t.setName("LogWriter");
@ -196,8 +196,9 @@ public class LogManager {
}
public void setConfig(String filename) {
_log.debug("Config filename set to " + filename);
_location = filename;
_locationFile = new File(filename);
if (!_locationFile.isAbsolute())
_locationFile = new File(_context.getConfigDir(), filename);
loadConfig();
}
@ -232,20 +233,12 @@ public class LogManager {
loadConfig();
}
///
///
//
//
//
private void loadConfig() {
File cfgFile = new File(_location);
File cfgFile = _locationFile;
if (!cfgFile.exists()) {
if (!_alreadyNoticedMissingConfig) {
if (_log.shouldLog(Log.WARN))
_log.warn("Log file " + _location + " does not exist");
//System.err.println("Log file " + _location + " does not exist");
_log.warn("Log file " + _locationFile.getAbsolutePath() + " does not exist");
_alreadyNoticedMissingConfig = true;
}
parseConfig(new Properties());
@ -262,9 +255,6 @@ public class LogManager {
return;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Loading config from " + _location);
Properties p = new Properties();
FileInputStream fis = null;
try {
@ -272,7 +262,7 @@ public class LogManager {
p.load(fis);
_configLastRead = _context.clock().now();
} catch (IOException ioe) {
System.err.println("Error loading logger config from " + new File(_location).getAbsolutePath());
System.err.println("Error loading logger config from " + cfgFile.getAbsolutePath());
} finally {
if (fis != null) try {
fis.close();
@ -540,7 +530,7 @@ public class LogManager {
String config = createConfig();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(_location);
fos = new FileOutputStream(_locationFile);
fos.write(config.getBytes());
return true;
} catch (IOException ioe) {

View File

@ -15,6 +15,8 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import net.i2p.I2PAppContext;
/**
* Log writer thread that pulls log records from the LogManager, writes them to
* the current logfile, and rotates the logs as necessary. This also periodically
@ -22,6 +24,7 @@ import java.util.List;
*
*/
class LogWriter implements Runnable {
/** every 10 seconds? why? Just have the gui force a reread after a change?? */
private final static long CONFIG_READ_ITERVAL = 10 * 1000;
private long _lastReadConfig = 0;
private long _numBytesInCurrentFile = 0;
@ -38,6 +41,7 @@ class LogWriter implements Runnable {
public LogWriter(LogManager manager) {
_manager = manager;
_lastReadConfig = Clock.getInstance().now();
}
public void stopWriting() {
@ -168,15 +172,21 @@ class LogWriter implements Runnable {
*
*/
private File getNextFile(String pattern) {
File f = null;
File f = new File(pattern);
File base = null;
if (!f.isAbsolute())
base = I2PAppContext.getGlobalContext().getLogDir();
if ( (pattern.indexOf('#') < 0) && (pattern.indexOf('@') <= 0) ) {
return new File(pattern);
if (base != null)
return new File(base, pattern);
else
return f;
}
int max = _manager.getRotationLimit();
if (_rotationNum == -1) {
return getFirstFile(pattern, max);
return getFirstFile(base, pattern, max);
}
// we're in rotation, just go to the next
@ -190,9 +200,13 @@ class LogWriter implements Runnable {
* Retrieve the first file, updating the rotation number accordingly
*
*/
private File getFirstFile(String pattern, int max) {
private File getFirstFile(File base, String pattern, int max) {
for (int i = 0; i < max; i++) {
File f = new File(replace(pattern, i));
File f;
if (base != null)
f = new File(base, replace(pattern, i));
else
f = new File(replace(pattern, i));
if (!f.exists()) {
_rotationNum = i;
return f;
@ -202,7 +216,11 @@ class LogWriter implements Runnable {
// all exist, pick the oldest to replace
File oldest = null;
for (int i = 0; i < max; i++) {
File f = new File(replace(pattern, i));
File f;
if (base != null)
f = new File(base, replace(pattern, i));
else
f = new File(replace(pattern, i));
if (oldest == null) {
oldest = f;
} else {

View File

@ -540,9 +540,10 @@ public class NativeBigInteger extends BigInteger {
FileOutputStream fos = null;
try {
InputStream libStream = resource.openStream();
outFile = new File(_libPrefix + "jbigi" + _libSuffix);
outFile = new File(I2PAppContext.getGlobalContext().getBaseDir(), _libPrefix + "jbigi" + _libSuffix);
fos = new FileOutputStream(outFile);
byte buf[] = new byte[4096*1024];
// wtf this was 4096*1024 which is really excessive for a roughly 50KB file
byte buf[] = new byte[4096];
while (true) {
int read = libStream.read(buf);
if (read < 0) break;

View File

@ -142,7 +142,7 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
private static final String SEEDFILE = "prngseed.rnd";
public static final void writeSeed(byte buf[]) {
File f = new File(SEEDFILE);
File f = new File(I2PAppContext.getGlobalContext().getConfigDir(), SEEDFILE);
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
@ -164,7 +164,7 @@ public class RandomSource extends SecureRandom implements EntropyHarvester {
}
private static final boolean seedFromFile(String filename, byte buf[]) {
File f = new File(filename);
File f = new File(I2PAppContext.getGlobalContext().getConfigDir(), filename);
if (f.exists()) {
FileInputStream fis = null;
try {

View File

@ -0,0 +1,481 @@
package net.i2p.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Properties;
import net.i2p.data.DataHelper;
/**
* Get a working directory for i2p.
*
* For the location, first try the system property i2p.dir.config
* Next try $HOME/.i2p on linux or %APPDATA%\I2P on Windows.
*
* If the dir exists, return it.
* Otherwise, attempt to create it, and copy files from the base directory.
* To successfully copy, the base install dir must be the system property i2p.dir.base
* or else must be in $CWD.
*
* If I2P was run from the install directory in the past,
* and migrateOldData = true, copy the
* necessary data files (except i2psnark/) over to the new working directory.
*
* Otherwise, just copy over a limited number of files over.
*
* Do not ever copy or move the old i2psnark/ directory, as if the
* old and new locations are on different file systems, this could
* be quite slow.
*
* Modify some files while copying, see methods below.
*
* After migration, the router will run using the new directory.
* The wrapper, however, must be stopped and restarted from the new script - until then,
* it will continue to write to wrapper.log* in the old directory.
*
* @param whether to copy all data over from an existing install
*/
public class WorkingDir {
private final static String PROP_BASE_DIR = "i2p.dir.base";
private final static String PROP_WORKING_DIR = "i2p.dir.config";
private final static String WORKING_DIR_DEFAULT_WINDOWS = "I2P";
private final static String WORKING_DIR_DEFAULT = ".i2p";
/**
* Only call this once on router invocation.
* Caller should store the return value for future reference.
*/
public static String getWorkingDir(boolean migrateOldData) {
String dir = System.getProperty(PROP_WORKING_DIR);
boolean isWindows = System.getProperty("os.name").startsWith("Win");
File dirf = null;
if (dir != null) {
dirf = new File(dir);
} else {
String home = System.getProperty("user.home");
if (isWindows) {
String appdata = System.getenv("APPDATA");
if (appdata != null)
home = appdata;
dirf = new File(home, WORKING_DIR_DEFAULT_WINDOWS);
} else {
dirf = new File(home, WORKING_DIR_DEFAULT);
}
}
// where we are now
String cwd = System.getProperty(PROP_BASE_DIR);
if (cwd == null)
cwd = System.getProperty("user.dir");
// where we want to go
String rv = dirf.getAbsolutePath();
if (dirf.exists()) {
if (dirf.isDirectory())
return rv; // all is good, we found the user directory
System.err.println("Wanted to use " + rv + " for a working directory but it is not a directory");
return cwd;
}
if (!dirf.mkdir()) {
System.err.println("Wanted to use " + rv + " for a working directory but could not create it");
return cwd;
}
// Check for a hosts.txt file, if it exists then I2P is there
File oldDirf = new File(cwd);
File test = new File(oldDirf, "hosts.txt");
if (!test.exists()) {
System.err.println("ERROR - Cannot find I2P installation in " + cwd);
return cwd;
}
// Check for a router.keys file, if it exists it's an old install,
// and only migrate the data files if told to do so
test = new File(oldDirf, "router.keys");
boolean oldInstall = test.exists();
migrateOldData &= oldInstall;
// Do the copying
if (migrateOldData)
System.err.println("Migrating data files to new user directory " + rv);
else
System.err.println("Setting up new user directory " + rv);
boolean success = migrate(MIGRATE_BASE, oldDirf, dirf);
// this one must be after MIGRATE_BASE
success &= migrateJettyXml(oldDirf, dirf);
success &= migrateWrapperConfig(oldDirf, dirf);
if (migrateOldData) {
success &= migrate(MIGRATE_DATA, oldDirf, dirf);
success &= migrateI2PTunnelKeys(oldDirf, dirf);
success &= migrateSnark(oldDirf, dirf);
// new installs will have updated scripts left in the install dir
// don't bother migrating runplain.sh or i2prouter.bat
if (!isWindows)
success &= migrateI2prouter(oldDirf, dirf);
} else if (!oldInstall) {
// copy the default i2psnark.config over
success &= migrate("i2psnark.config", oldDirf, dirf);
}
// Report success or failure
if (success) {
System.err.println("Successfully copied data files to new user directory " + rv);
if (migrateOldData) {
System.err.println("Libraries and other files remain in the old directory " + cwd + ", do not remove them.");
System.err.println("You should manually move any non-standard files, such as additional eepsite directories and key files");
System.err.println("After verifying that all is working, you may delete the following data files and directories in " +
cwd + ": " + MIGRATE_DATA.replace(',', ' ') + " i2psnark.config tmp work");
if (System.getProperty("wrapper.version") != null)
System.err.println("Note that until you shutdown your router completely and restart, the wrapper will continue" +
" to log to the old wrapper logs in " + cwd);
if (!isWindows)
System.err.println("From now on, you should now use the i2prouter" +
" script in the " + rv + " directory to start i2p." +
" You may copy or move this script elsewhere, you need not run it from that directory.");
}
return rv;
} else {
System.err.println("FAILED copy of some or all data files to new directory " + rv);
System.err.println("Check logs for details");
System.err.println("Continung to use data files in old directory " + cwd);
return cwd;
}
}
/**
* files and directories from the base install to copy over
* None of these should be included in i2pupdate.zip
*
* The user should not delete these in the old location, leave them as templates for new users
*/
private static final String MIGRATE_BASE =
// base install - dirs
// We don't currently have a default addressbook/ in the base distribution,
// but distros might put one in
"addressbook,eepsite," +
// base install - files
// We don't currently have a default router.config or logger.config in the base distribution,
// but distros might put one in
"blocklist.txt,clients.config,hosts.txt,i2ptunnel.config,jetty-i2psnark.xml," +
"logger.config,router.config,systray.config";
/**
* files and directories from an old single-directory installation to copy over - NOT including snark
* None of these should be included in i2pupdate.zip
*
* The user can be advised to delete these from the old location
*/
private static final String MIGRATE_DATA =
// post install - dirs
// not required to copy - tmp/, work/
// addressbook included in MIGRATE_BASE above
"keyBackup,logs,netDb,peerProfiles," +
// post install - files
// not required to copy - prngseed.rnd
// logger.config and router.config included in MIGRATE_BASE above
"bob.config,privatehosts.txt,router.info,router.keys," +
"sam.keys,susimail.config,userhosts.txt,webapps.config," +
"wrapper.log,wrapper.log.1,wrapper.log.2";
private static boolean migrate(String list, File olddir, File todir) {
boolean rv = true;
String files[] = list.split(",");
for (int i = 0; i < files.length; i++) {
File from = new File(olddir, files[i]);
if (!copy(from, todir)) {
System.err.println("Error copying " + from.getAbsolutePath());
rv = false;
}
}
return rv;
}
/**
* Copy over the i2psnark.config file with modifications
*/
private static boolean migrateSnark(File olddir, File todir) {
boolean rv = true;
File oldSnark = new File(olddir, "i2psnark");
File newSnark = new File(todir, "i2psnark");
File oldSnarkConfig = new File(olddir, "i2psnark.config");
File newSnarkConfig = new File(todir, "i2psnark.config");
boolean hasData = oldSnark.exists();
if (hasData) {
File children[] = oldSnark.listFiles();
hasData = children != null && children.length > 0;
}
if (oldSnarkConfig.exists()) {
if (hasData) {
// edit the snark config file to point to the old location, we aren't moving the data
try {
Properties props = new Properties();
DataHelper.loadProps(props, oldSnarkConfig);
String dir = props.getProperty("i2psnark.dir");
if (dir == null)
dir = "i2psnark";
// change relative to absolute path
File f = new File(dir);
props.setProperty("i2psnark.dir", f.getAbsolutePath());
DataHelper.storeProps(props, newSnarkConfig);
System.err.println("Copied i2psnark.config with modifications");
} catch (IOException ioe) {
System.err.println("FAILED copy i2psnark.config");
rv = false;
}
} else {
// copy the i2psnark config file over
copy(newSnarkConfig, todir);
System.err.println("Copied i2psnark.config");
}
} else {
if (hasData) {
// data but no previous config file (unlikely) - make new config file
try {
Properties props = new Properties();
File f = new File("i2psnark");
props.setProperty("i2psnark.dir", f.getAbsolutePath());
DataHelper.storeProps(props, newSnarkConfig);
} catch (IOException ioe) {
// ignore
}
} // else no config and no data
}
if (hasData) {
/*************
// crude attempt to detect same filesystem
if ((oldSnark.getAbsolutePath().startsWith("/home/") && newSnark.getAbsolutePath().startsWith("/home/")) ||
(System.getProperty("os.name").toLowerCase.indexOf("windows") >= 0 &&
oldSnark.getAbsolutePath().substring(0,1).equals(newSnark.getAbsolutePath().substring(0,1) &&
oldSnark.getAbsolutePath().substring(1,2).equals(":\\") &&
newSnark.getAbsolutePath().substring(1,2).equals(":\\"))) {
// OK, apparently in same file system
// move everything
}
**************/
System.err.println("NOT moving the i2psnark data directory " + oldSnark.getAbsolutePath() +
" to the new directory " + newSnark.getAbsolutePath() +
". You may move the directory contents manually WHILE I2P IS NOT RUNNING," +
" and edit the file " + newSnarkConfig.getAbsolutePath() +
" to configure i2psnark to use a different location by editing the i2psnark.dir configuration to be" +
" i2psnark.dir=" + oldSnark.getAbsolutePath() +
" and restart, or you may leave the i2psnark directory in its old location.");
}
return true;
}
/**
* Copy over the i2prouter file with modifications
* The resulting script can be run from any location.
*/
private static boolean migrateI2prouter(File olddir, File todir) {
File oldFile = new File(olddir, "i2prouter");
File newFile = new File(todir, "i2prouter");
FileInputStream in = null;
PrintWriter out = null;
try {
in = new FileInputStream(oldFile);
out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
boolean firstTime = true;
String s = null;
while ((s = DataHelper.readLine(in)) != null) {
if (s.equals("WRAPPER_CMD=\"./i2psvc\"")) {
// i2psvc in the old location
File f = new File("i2psvc");
s = "WRAPPER_CMD=\"" + f.getAbsolutePath() + "\"";
} else if(s.equals("WRAPPER_CONF=\"wrapper.config\"")) {
// wrapper.config the new location
File f = new File(todir, "wrapper.config");
s = "WRAPPER_CONF=\"" + f.getAbsolutePath() + "\"";
} else if(s.equals("PIDDIR=\".\"")) {
// i2p.pid in the new location
s = "PIDDIR=\"" + todir.getAbsolutePath() + "\"";
}
out.println(s);
if (firstTime) {
// first line was #!/bin/sh, so had to wait until now
out.println("# Modified by I2P User dir migration script");
firstTime = false;
}
}
System.err.println("Copied i2prouter with modifications");
return true;
} catch (IOException ioe) {
if (in != null) {
System.err.println("FAILED copy i2prouter");
return false;
}
return true;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) out.close();
}
}
/**
* Copy over the wrapper.config file with modifications
*/
private static boolean migrateWrapperConfig(File olddir, File todir) {
File oldFile = new File(olddir, "wrapper.config");
File newFile = new File(todir, "wrapper.config");
FileInputStream in = null;
PrintWriter out = null;
try {
in = new FileInputStream(oldFile);
out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
out.println("# Modified by I2P User dir migration script");
String s = null;
// Don't use replaceFirst because backslashes in the replacement string leads to havoc
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4689750
// "Note that backslashes and dollar signs in the replacement string may cause the results
// to be different than if it were being treated as a literal replacement string.
// Dollar signs may be treated as references to captured subsequences as described above,
// and backslashes are used to escape literal characters in the replacement string."
while ((s = DataHelper.readLine(in)) != null) {
if (s.startsWith("wrapper.java.classpath.")) {
// libraries in the old location
s = s.replace("=lib/", '=' + olddir.getAbsolutePath() + File.separatorChar + "lib" + File.separatorChar);
} else if (s.startsWith("wrapper.java.library.path.")) {
// libraries in the old location
if (s.contains("=."))
s = s.replace("=.", '=' + olddir.getAbsolutePath());
else if (s.contains("=lib"))
s = s.replace("=lib", '=' + olddir.getAbsolutePath() + File.separatorChar + "lib");
} else if (s.startsWith("wrapper.logfile=wrapper.log")) {
// wrapper logs in the new location
s = s.replace("=", '=' + todir.getAbsolutePath() + File.separatorChar);
} else if (s.startsWith("wrapper.pidfile=i2p.pid")) {
// i2p.pid in the new location
s = s.replace("=", '=' + todir.getAbsolutePath() + File.separatorChar);
}
out.println(s);
}
System.err.println("Copied wrapper.config with modifications");
return true;
} catch (IOException ioe) {
if (in != null) {
System.err.println("FAILED copy wrapper.config");
return false;
}
return false;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) out.close();
}
}
/**
* Copy over the jetty.xml file with modifications
* It was already copied over once in migrate(), throw that out and
* do it again with modifications.
*/
private static boolean migrateJettyXml(File olddir, File todir) {
File eepsite1 = new File(olddir, "eepsite");
File oldFile = new File(eepsite1, "jetty.xml");
File eepsite2 = new File(todir, "eepsite");
File newFile = new File(eepsite2, "jetty.xml");
FileInputStream in = null;
PrintWriter out = null;
try {
in = new FileInputStream(oldFile);
out = new PrintWriter(new BufferedWriter(new FileWriter(newFile)));
String s = null;
while ((s = DataHelper.readLine(in)) != null) {
if (s.indexOf("./eepsite/") >= 0) {
s = s.replace("./eepsite/", todir.getAbsolutePath() + File.separatorChar + "eepsite" + File.separatorChar);
}
out.println(s);
}
out.println("<!-- Modified by I2P User dir migration script -->");
System.err.println("Copied jetty.xml with modifications");
return true;
} catch (IOException ioe) {
if (in != null) {
System.err.println("FAILED copy jetty.xml");
return false;
}
return false;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) out.close();
}
}
/**
* Relatively recent default i2ptunnel key file name
*/
private static boolean migrateI2PTunnelKeys(File olddir, File todir) {
for (int i = 0; i < 100; i++) {
copy(new File(olddir, "i2ptunnel" + i + "-privKeys.dat"), todir);
}
return true;
}
/**
* Recursive copy a file or dir to a dir
*
* @param src file or directory, need not exist
* @param target the directory to copy to, will be created if it doesn't exist
* @return true for success OR if src does not exist
*/
public static final boolean copy(File src, File targetDir) {
if (!src.exists())
return true;
if (!targetDir.exists()) {
if (!targetDir.mkdir()) {
System.err.println("FAILED copy " + src.getPath());
return false;
}
}
File targetFile = new File(targetDir, src.getName());
if (!src.isDirectory())
return copyFile(src, targetFile);
File children[] = src.listFiles();
if (children == null) {
System.err.println("FAILED copy " + src.getPath());
return false;
}
boolean rv = true;
for (int i = 0; i < children.length; i++) {
rv &= copy(children[i], targetFile);
}
return rv;
}
/**
* @param src not a directory, must exist
* @param dest not a directory, will be overwritten if existing
* @@reurn true if it was copied successfully
*/
public static boolean copyFile(File src, File dst) {
if (!src.exists()) return false;
boolean rv = true;
byte buf[] = new byte[4096];
FileInputStream in = null;
FileOutputStream out = null;
try {
in = new FileInputStream(src);
out = new FileOutputStream(dst);
int read = 0;
while ( (read = in.read(buf)) != -1)
out.write(buf, 0, read);
System.err.println("Copied " + src.getPath());
} catch (IOException ioe) {
System.err.println("FAILED copy " + src.getPath());
rv = false;
} finally {
if (in != null) try { in.close(); } catch (IOException ioe) {}
if (out != null) try { out.close(); } catch (IOException ioe) {}
}
if (rv)
dst.setLastModified(src.lastModified());
return rv;
}
}