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<String, String> config = new HashMap<String, String>(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"); File jbigiJar = new File(context.getBaseDir(), "lib/jbigi.jar"); if (goodOS && jbigiJar.exists()) { String libPrefix = (isWin ? "" : "lib"); String libSuffix = (isWin ? ".dll" : isMac ? ".jnilib" : ".so"); if (isX86) { 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"); } } } } if (isX86 || SystemVersion.isARM()) { 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.isFile()) { 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"); } } } }