diff --git a/router/java/src/net/i2p/router/Router.java b/router/java/src/net/i2p/router/Router.java index 4916d8d72..c16e5ba4a 100644 --- a/router/java/src/net/i2p/router/Router.java +++ b/router/java/src/net/i2p/router/Router.java @@ -8,15 +8,10 @@ package net.i2p.router; * */ -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; -import java.io.InputStreamReader; -import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -49,7 +44,6 @@ import net.i2p.router.util.EventLog; import net.i2p.stat.RateStat; import net.i2p.stat.StatManager; import net.i2p.util.ByteCache; -import net.i2p.util.FileUtil; import net.i2p.util.FortunaRandomSource; import net.i2p.util.I2PAppThread; import net.i2p.util.I2PThread; @@ -113,6 +107,7 @@ public class Router implements RouterClock.ClockShiftListener { private final static String DNS_CACHE_TIME = "" + (5*60); private static final String EVENTLOG = "eventlog.txt"; private static final String PROP_JBIGI = "jbigi.loadedResource"; + public static final String UPDATE_FILE = "i2pupdate.zip"; private static final String originalTimeZoneID; static { @@ -1495,199 +1490,11 @@ public class Router implements RouterClock.ClockShiftListener { // If it does an update, it never returns. // I guess it's better to have the other-router check above this, we don't want to // overwrite an existing running router's jar files. Other than ours. - r.installUpdates(); + InstallUpdate.installUpdates(r); // ********* Start no threads before here ********* // r.runRouter(); } } - - public static final String UPDATE_FILE = "i2pupdate.zip"; - private static final String DELETE_FILE = "deletelist.txt"; - - /** - * Context must be available. - * Unzip update file found in the router dir OR base dir, to the base dir - * - * If successfull, will call exit() and never return. - * - * If we can't write to the base dir, complain. - * Note: _log not available here. - */ - private void installUpdates() { - File updateFile = new File(_context.getRouterDir(), UPDATE_FILE); - boolean exists = updateFile.exists(); - if (!exists) { - updateFile = new File(_context.getBaseDir(), UPDATE_FILE); - exists = updateFile.exists(); - } - if (exists) { - // do a simple permissions test, if it fails leave the file in place and don't restart - File test = new File(_context.getBaseDir(), "history.txt"); - if ((test.exists() && !test.canWrite()) || (!_context.getBaseDir().canWrite())) { - System.out.println("ERROR: No write permissions on " + _context.getBaseDir() + - " to extract software update file"); - // carry on - return; - } - System.out.println("INFO: Update file exists [" + UPDATE_FILE + "] - installing"); - // verify the whole thing first - // we could remember this fails, and not bother restarting, but who cares... - boolean ok = FileUtil.verifyZip(updateFile); - if (ok) { - // This may be useful someday. First added in 0.8.2 - // Moved above the extract so we don't NCDFE - _config.put("router.updateLastInstalled", "" + System.currentTimeMillis()); - // Set the last version to the current version, since 0.8.13 - _config.put("router.previousVersion", RouterVersion.VERSION); - _config.put("router.previousFullVersion", RouterVersion.FULL_VERSION); - saveConfig(); - ok = FileUtil.extractZip(updateFile, _context.getBaseDir()); - } - - // Very important - we have now trashed our jars. - // After this point, do not use any new I2P classes, or they will fail to load - // and we will die with NCDFE. - // Ideally, do not use I2P classes at all, new or not. - try { - if (ok) { - // We do this here so we may delete old jars before we restart - deleteListedFiles(); - System.out.println("INFO: Update installed"); - } else { - System.out.println("ERROR: Update failed!"); - } - if (!ok) { - // we can't leave the file in place or we'll continually restart, so rename it - File bad = new File(_context.getRouterDir(), "BAD-" + UPDATE_FILE); - boolean renamed = updateFile.renameTo(bad); - if (renamed) { - System.out.println("Moved update file to " + bad.getAbsolutePath()); - } else { - System.out.println("Deleting file " + updateFile.getAbsolutePath()); - ok = true; // so it will be deleted - } - } - if (ok) { - boolean deleted = updateFile.delete(); - if (!deleted) { - System.out.println("ERROR: Unable to delete the update file!"); - updateFile.deleteOnExit(); - } - } - // exit whether ok or not - if (_context.hasWrapper()) - System.out.println("INFO: Restarting after update"); - else - System.out.println("WARNING: Exiting after update, restart I2P"); - } catch (Throwable t) { - // hide the NCDFE - // hopefully the update file got deleted or we will loop - } - System.exit(EXIT_HARD_RESTART); - } else { - deleteJbigiFiles(); - // It was here starting in 0.8.12 so it could be used the very first time - // Now moved up so it is usually run only after an update - // But the first time before jetty 6 it will run here... - // Here we can't remove jars - deleteListedFiles(); - } - } - - /** - * Remove extracted libjbigi.so and libjcpuid.so files if we have a newer jbigi.jar, - * so the new ones will be extracted. - * We do this after the restart, not after the extract, because it's safer, and - * because people may upgrade their jbigi.jar file manually. - * - * Copied from NativeBigInteger, which we can't access here or the - * libs will get loaded. - */ - private void deleteJbigiFiles() { - boolean isX86 = SystemVersion.isX86(); - String osName = System.getProperty("os.name").toLowerCase(Locale.US); - boolean isWin = SystemVersion.isWindows(); - boolean isMac = SystemVersion.isMac(); - // only do this on these OSes - boolean goodOS = isWin || isMac || - osName.contains("linux") || osName.contains("freebsd"); - - // only do this on these x86 - File jbigiJar = new File(_context.getBaseDir(), "lib/jbigi.jar"); - if (isX86 && goodOS && jbigiJar.exists()) { - String libPrefix = (isWin ? "" : "lib"); - String libSuffix = (isWin ? ".dll" : isMac ? ".jnilib" : ".so"); - - File jcpuidLib = new File(_context.getBaseDir(), libPrefix + "jcpuid" + libSuffix); - if (jcpuidLib.canWrite() && jbigiJar.lastModified() > jcpuidLib.lastModified()) { - String path = jcpuidLib.getAbsolutePath(); - boolean success = FileUtil.copy(path, path + ".bak", true, true); - if (success) { - boolean success2 = jcpuidLib.delete(); - if (success2) { - System.out.println("New jbigi.jar detected, moved jcpuid library to " + - path + ".bak"); - System.out.println("Check logs for successful installation of new library"); - } - } - } - - File jbigiLib = new File(_context.getBaseDir(), libPrefix + "jbigi" + libSuffix); - if (jbigiLib.canWrite() && jbigiJar.lastModified() > jbigiLib.lastModified()) { - String path = jbigiLib.getAbsolutePath(); - boolean success = FileUtil.copy(path, path + ".bak", true, true); - if (success) { - boolean success2 = jbigiLib.delete(); - if (success2) { - System.out.println("New jbigi.jar detected, moved jbigi library to " + - path + ".bak"); - System.out.println("Check logs for successful installation of new library"); - } - } - } - } - } - - /** - * Delete all files listed in the delete file. - * Format: One file name per line, comment lines start with '#'. - * All file names must be relative to $I2P, absolute file names not allowed. - * We probably can't remove old jars this way. - * Fails silently. - * Use no new I2P classes here so it may be called after zip extraction. - * @since 0.8.12 - */ - private void deleteListedFiles() { - File deleteFile = new File(_context.getBaseDir(), DELETE_FILE); - if (!deleteFile.exists()) - return; - // this is similar to FileUtil.readTextFile() but we can't use any I2P classes here - FileInputStream fis = null; - BufferedReader in = null; - try { - fis = new FileInputStream(deleteFile); - in = new BufferedReader(new InputStreamReader(fis, "UTF-8")); - String line; - while ( (line = in.readLine()) != null) { - String fl = line.trim(); - if (fl.contains("..") || fl.startsWith("#") || fl.length() == 0) - continue; - File df = new File(fl); - if (df.isAbsolute()) - continue; - df = new File(_context.getBaseDir(), fl); - if (df.exists() && !df.isDirectory()) { - if (df.delete()) - System.out.println("INFO: File [" + fl + "] deleted"); - } - } - } catch (IOException ioe) {} - finally { - if (in != null) try { in.close(); } catch(IOException ioe) {} - if (deleteFile.delete()) - System.out.println("INFO: File [" + DELETE_FILE + "] deleted"); - } - } /******* private static void verifyWrapperConfig() { diff --git a/router/java/src/net/i2p/router/tasks/InstallUpdate.java b/router/java/src/net/i2p/router/tasks/InstallUpdate.java new file mode 100644 index 000000000..b4bea6770 --- /dev/null +++ b/router/java/src/net/i2p/router/tasks/InstallUpdate.java @@ -0,0 +1,215 @@ +package net.i2p.router.tasks; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.router.RouterVersion; +import net.i2p.util.FileUtil; +import net.i2p.util.SystemVersion; + +/** + * If the i2pupdate.zip file is present, + * unzip it and JVM exit. + * + * @since 0.9.20 moved from Router.java + */ +public class InstallUpdate { + + private static final String DELETE_FILE = "deletelist.txt"; + + /** + * Context must be available. + * Unzip update file found in the router dir OR base dir, to the base dir + * + * If successful, will call exit() and never return. + * + * If we can't write to the base dir, write message to System.out and return. + * Note: _log not available here. + */ + public static void installUpdates(Router r) { + RouterContext context = r.getContext(); + File updateFile = new File(context.getRouterDir(), Router.UPDATE_FILE); + boolean exists = updateFile.exists(); + if (!exists) { + updateFile = new File(context.getBaseDir(), Router.UPDATE_FILE); + exists = updateFile.exists(); + } + if (exists) { + // do a simple permissions test, if it fails leave the file in place and don't restart + File test = new File(context.getBaseDir(), "history.txt"); + if ((test.exists() && !test.canWrite()) || (!context.getBaseDir().canWrite())) { + System.out.println("ERROR: No write permissions on " + context.getBaseDir() + + " to extract software update file"); + // carry on + return; + } + System.out.println("INFO: Update file exists [" + Router.UPDATE_FILE + "] - installing"); + // verify the whole thing first + // we could remember this fails, and not bother restarting, but who cares... + boolean ok = FileUtil.verifyZip(updateFile); + if (ok) { + // This may be useful someday. First added in 0.8.2 + // Moved above the extract so we don't NCDFE + Map config = new HashMap(4); + config.put("router.updateLastInstalled", "" + System.currentTimeMillis()); + // Set the last version to the current version, since 0.8.13 + config.put("router.previousVersion", RouterVersion.VERSION); + config.put("router.previousFullVersion", RouterVersion.FULL_VERSION); + r.saveConfig(config, null); + ok = FileUtil.extractZip(updateFile, context.getBaseDir()); + } + + // Very important - we have now trashed our jars. + // After this point, do not use any new I2P classes, or they will fail to load + // and we will die with NCDFE. + // Ideally, do not use I2P classes at all, new or not. + try { + if (ok) { + // We do this here so we may delete old jars before we restart + deleteListedFiles(context); + System.out.println("INFO: Update installed"); + } else { + System.out.println("ERROR: Update failed!"); + } + if (!ok) { + // we can't leave the file in place or we'll continually restart, so rename it + File bad = new File(context.getRouterDir(), "BAD-" + Router.UPDATE_FILE); + boolean renamed = updateFile.renameTo(bad); + if (renamed) { + System.out.println("Moved update file to " + bad.getAbsolutePath()); + } else { + System.out.println("Deleting file " + updateFile.getAbsolutePath()); + ok = true; // so it will be deleted + } + } + if (ok) { + boolean deleted = updateFile.delete(); + if (!deleted) { + System.out.println("ERROR: Unable to delete the update file!"); + updateFile.deleteOnExit(); + } + } + // exit whether ok or not + if (context.hasWrapper()) + System.out.println("INFO: Restarting after update"); + else + System.out.println("WARNING: Exiting after update, restart I2P"); + } catch (Throwable t) { + // hide the NCDFE + // hopefully the update file got deleted or we will loop + } + System.exit(Router.EXIT_HARD_RESTART); + } else { + deleteJbigiFiles(context); + // It was here starting in 0.8.12 so it could be used the very first time + // Now moved up so it is usually run only after an update + // But the first time before jetty 6 it will run here... + // Here we can't remove jars + deleteListedFiles(context); + } + } + + /** + * Remove extracted libjbigi.so and libjcpuid.so files if we have a newer jbigi.jar, + * so the new ones will be extracted. + * We do this after the restart, not after the extract, because it's safer, and + * because people may upgrade their jbigi.jar file manually. + * + * Copied from NativeBigInteger, which we can't access here or the + * libs will get loaded. + */ + private static void deleteJbigiFiles(RouterContext context) { + boolean isX86 = SystemVersion.isX86(); + String osName = System.getProperty("os.name").toLowerCase(Locale.US); + boolean isWin = SystemVersion.isWindows(); + boolean isMac = SystemVersion.isMac(); + // only do this on these OSes + boolean goodOS = isWin || isMac || + osName.contains("linux") || osName.contains("freebsd"); + + // only do this on these x86 + File jbigiJar = new File(context.getBaseDir(), "lib/jbigi.jar"); + if (isX86 && goodOS && jbigiJar.exists()) { + String libPrefix = (isWin ? "" : "lib"); + String libSuffix = (isWin ? ".dll" : isMac ? ".jnilib" : ".so"); + + File jcpuidLib = new File(context.getBaseDir(), libPrefix + "jcpuid" + libSuffix); + if (jcpuidLib.canWrite() && jbigiJar.lastModified() > jcpuidLib.lastModified()) { + String path = jcpuidLib.getAbsolutePath(); + boolean success = FileUtil.copy(path, path + ".bak", true, true); + if (success) { + boolean success2 = jcpuidLib.delete(); + if (success2) { + System.out.println("New jbigi.jar detected, moved jcpuid library to " + + path + ".bak"); + System.out.println("Check logs for successful installation of new library"); + } + } + } + + File jbigiLib = new File(context.getBaseDir(), libPrefix + "jbigi" + libSuffix); + if (jbigiLib.canWrite() && jbigiJar.lastModified() > jbigiLib.lastModified()) { + String path = jbigiLib.getAbsolutePath(); + boolean success = FileUtil.copy(path, path + ".bak", true, true); + if (success) { + boolean success2 = jbigiLib.delete(); + if (success2) { + System.out.println("New jbigi.jar detected, moved jbigi library to " + + path + ".bak"); + System.out.println("Check logs for successful installation of new library"); + } + } + } + } + } + + /** + * Delete all files listed in the delete file. + * Format: One file name per line, comment lines start with '#'. + * All file names must be relative to $I2P, absolute file names not allowed. + * We probably can't remove old jars this way. + * Fails silently. + * Use no new I2P classes here so it may be called after zip extraction. + * @since 0.8.12 + */ + private static void deleteListedFiles(RouterContext context) { + File deleteFile = new File(context.getBaseDir(), DELETE_FILE); + if (!deleteFile.exists()) + return; + // this is similar to FileUtil.readTextFile() but we can't use any I2P classes here + FileInputStream fis = null; + BufferedReader in = null; + try { + fis = new FileInputStream(deleteFile); + in = new BufferedReader(new InputStreamReader(fis, "UTF-8")); + String line; + while ( (line = in.readLine()) != null) { + String fl = line.trim(); + if (fl.contains("..") || fl.startsWith("#") || fl.length() == 0) + continue; + File df = new File(fl); + if (df.isAbsolute()) + continue; + df = new File(context.getBaseDir(), fl); + if (df.exists() && !df.isDirectory()) { + if (df.delete()) + System.out.println("INFO: File [" + fl + "] deleted"); + } + } + } catch (IOException ioe) {} + finally { + if (in != null) try { in.close(); } catch(IOException ioe) {} + if (deleteFile.delete()) + System.out.println("INFO: File [" + DELETE_FILE + "] deleted"); + } + } +} + diff --git a/router/java/src/net/i2p/router/tasks/package.html b/router/java/src/net/i2p/router/tasks/package.html index e032edd6c..bc1a92730 100644 --- a/router/java/src/net/i2p/router/tasks/package.html +++ b/router/java/src/net/i2p/router/tasks/package.html @@ -4,6 +4,7 @@ Miscellaneous classes, mostly things that are executed periodically as Jobs, Threads, and SimpleTimer.TimedEvents. These are used only by Router.java. +Nothing here is to be used externally, not a part of the public API.