/** * WTFPL * Version 2, December 2004 * * Copyright (C) sponge * Planet Earth * * See... * * http://sam.zoy.org/wtfpl/ * and * http://en.wikipedia.org/wiki/WTFPL * * ...for any additional details and license questions. */ package net.i2p.BOB; import static net.i2p.app.ClientAppState.*; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.ConnectException; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URL; import java.util.Arrays; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import java.util.jar.Attributes; import java.util.jar.Manifest; import net.i2p.I2PAppContext; import net.i2p.app.*; import net.i2p.client.I2PClient; import net.i2p.util.I2PAppThread; import net.i2p.util.SimpleTimer2; /** * <span style="font-size:8px;font-family:courier;color:#EEEEEE;background-color:#000000"> * ################################################################################<br> * ############################.#..........#..#..........##########################<br> * #######################......................................###################<br> * ####################...........................#.......#........################<br> * #################..................##...................#.........##############<br> * ###############................###...####.....#..###.....#.........#############<br> * #############...........###..#..###...#####...###.##........#.......############<br> * ###########................#......##...#####...##..##.......#..#........########<br> * ##########.........................#....##.##..#...##.....................######<br> * #########...................................#....#.........................#####<br> * ########.........................................#...............#..........####<br> * ########.........................................#..........#######..........###<br> * #######.................................................############..........##<br> * #######..........................................####################.........##<br> * #######............####################......########################.........##<br> * ######.............###############################################.##.........##<br> * ######............################################################..##........##<br> * ######............################################################..##........##<br> * ######.............##############################################..##.........##<br> * ######............##############################################...##..........#<br> * ######............#..###########################################...##..........#<br> * ######.............#############################################....#..........#<br> * #######...........###############################################..##.........##<br> * #######...........#####.#.#.#.########################.....#.####...##........##<br> * ######............#..............##################.................##.........#<br> * ######................####.........###############........#####......##........#<br> * ######..............####..#.........############.......##.#.######...##.......##<br> * ######.................#.####.........########...........##....###...##.......##<br> * #######....#....###...................#######...............#...###..##.......##<br> * #######.........###..###.....###.......######.##.#####.........####..##.......##<br> * #######.....#...##############.........############......###########.###......##<br> * #######....##...##########.......##...##############......#.############.....###<br> * ########....#..########......######...##################################....####<br> * ########....##.####################...##################################....####<br> * ########..#.##..###################..##################################..#..####<br> * ##########..###..#################...##################################...#.####<br> * #########....##...##############....########..#####.################.##..#.#####<br> * ############.##....##########.......#########.###.......###########..#.#########<br> * ###############.....#######...#.......########.....##.....######.....###########<br> * ###############......###....##..........##.......######....#.........#.#########<br> * ##############............##..................##########..............##########<br> * ##############..............................##########..#.............##########<br> * ###############.......##..................#####..............####....###########<br> * ###############.......#####.......#.............####.....#######.....###########<br> * ################...#...####......##################.....########....############<br> * ################...##..#####.........####.##.....#....##########....############<br> * ##################..##..####...........#####.#....############.....#############<br> * ##################......#####.................################....##############<br> * ###################.....####..........##########..###########....###############<br> * ####################..#..#..........................########.....###############<br> * #####################.##.......###.................########....#################<br> * ######################.........#.......#.##.###############....#################<br> * #############.#######...............#####################....###################<br> * ###..#.....##...####..........#.....####################....####################<br> * ####......##........................##################....######################<br> * #.##...###..............###.........###############......#######################<br> * #...###..##............######...........................########################<br> * ##.......###..........##########....#...#...........############################<br> * ##.........##.......############################################################<br> * ###........##.....##############################################################<br> * ####.............###############################################################<br> * ######.........#################################################################<br> * #########....###################################################################<br> * ################################################################################<br> * </span> * BOB, main command socket listener, launches the command parser engine. * * @author sponge */ public class BOB implements Runnable, ClientApp { public final static String PROP_CONFIG_LOCATION = "BOB.config"; public final static String PROP_BOB_PORT = "BOB.port"; public final static String PROP_BOB_HOST = "BOB.host"; public final static String PROP_CFG_VER = "BOB.CFG.VER"; /** unused when started via the ClientApp interface */ private static BOB _bob; private final NamedDB database; private final Properties props = new Properties(); private final AtomicBoolean spin = new AtomicBoolean(true); private static final String P_RUNNING = "RUNNING"; private static final String P_STARTING = "STARTING"; private static final String P_STOPPING = "STOPPING"; private final AtomicBoolean lock = new AtomicBoolean(false); // no longer used. // private static int maxConnections = 0; private final Logger _log; private final ClientAppManager _mgr; private final String[] _args; private volatile ClientAppState _state = UNINITIALIZED; private volatile ServerSocket listener; private volatile Thread _runner; /** * Stop BOB gracefully * @deprecated unused */ @Deprecated public synchronized static void stop() { if (_bob != null) _bob.shutdown(null); } /** * For ClientApp interface. * Does NOT open the listener socket or start threads; caller must call startup() * * @param mgr may be null * @param args non-null * @since 0.9.10 */ public BOB(I2PAppContext context, ClientAppManager mgr, String[] args) { // If we were run from command line, log to stdout boolean logToStdout = false; URL classResource = BOB.class.getResource("BOB.class"); if (classResource != null) { String classPath = classResource.toString(); if (classPath.startsWith("jar")) { String manifestPath = classPath.substring(0, classPath.lastIndexOf('!') + 1) + "/META-INF/MANIFEST.MF"; try { Manifest manifest = new Manifest(new URL(manifestPath).openStream()); Attributes attrs = manifest.getMainAttributes(); String mainClass = attrs.getValue("Main-Class"); if ("net.i2p.BOB.Main".equals(mainClass)) logToStdout = true; } catch (IOException ioe) {} } } _log = new Logger(context.logManager().getLog(BOB.class), logToStdout); _mgr = mgr; _args = args; _state = INITIALIZED; database = new NamedDB(); loadConfig(); } /** * Listen for incoming connections and handle them * * @param args */ public synchronized static void main(String[] args) { try { _bob = new BOB(I2PAppContext.getGlobalContext(), null, args); _bob.startup(); } catch (RuntimeException e) { e.printStackTrace(); throw e; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * @since 0.9.10 */ private void loadConfig() { int i = 0; boolean save = false; // Set up all defaults to be passed forward to other threads. // Re-reading the config file in each thread is pretty damn stupid. String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config"); // This is here just to ensure there is no interference with our threadgroups. SimpleTimer2 Y2 = SimpleTimer2.getInstance(); i = Y2.hashCode(); { File cfg = new File(configLocation); if (!cfg.isAbsolute()) { cfg = new File(I2PAppContext.getGlobalContext().getConfigDir(), configLocation); } FileInputStream fi = null; try { fi = new FileInputStream(cfg); props.load(fi); } catch (FileNotFoundException fnfe) { _log.warn("Unable to load up the BOB config file " + cfg.getAbsolutePath() + ", Using defaults.", fnfe); save = true; } catch (IOException ioe) { _log.warn("IOException on BOB config file " + cfg.getAbsolutePath() + ", using defaults.", ioe); } finally { if (fi != null) try { fi.close(); } catch (IOException ioe) {} } } // Global router and client API configurations that are missing are set to defaults here. if (!props.containsKey(I2PClient.PROP_TCP_HOST)) { props.setProperty(I2PClient.PROP_TCP_HOST, "localhost"); save = true; } if (!props.containsKey(I2PClient.PROP_TCP_PORT)) { props.setProperty(I2PClient.PROP_TCP_PORT, "7654"); save = true; } if (!props.containsKey(PROP_BOB_PORT)) { props.setProperty(PROP_BOB_PORT, "2827"); // 0xB0B save = true; } if (!props.containsKey("inbound.length")) { props.setProperty("inbound.length", "3"); save = true; } if (!props.containsKey("outbound.length")) { props.setProperty("outbound.length", "3"); save = true; } if (!props.containsKey("inbound.lengthVariance")) { props.setProperty("inbound.lengthVariance", "0"); save = true; } if (!props.containsKey("outbound.lengthVariance")) { props.setProperty("outbound.lengthVariance", "0"); save = true; } if (!props.containsKey(PROP_BOB_HOST)) { props.setProperty(PROP_BOB_HOST, "localhost"); save = true; } // PROP_RELIABILITY_NONE, PROP_RELIABILITY_BEST_EFFORT, PROP_RELIABILITY_GUARANTEED if (!props.containsKey(PROP_CFG_VER)) { props.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_NONE); props.setProperty(PROP_CFG_VER,"1"); save = true; } if (save) { File cfg = new File(configLocation); if (!cfg.isAbsolute()) { cfg = new File(I2PAppContext.getGlobalContext().getConfigDir(), configLocation); } FileOutputStream fo = null; try { _log.warn("Writing new defaults file " + cfg.getAbsolutePath()); fo = new FileOutputStream(cfg); props.store(fo, cfg.getAbsolutePath()); } catch (IOException ioe) { _log.error("IOException on BOB config file " + cfg.getAbsolutePath(), ioe); } finally { if (fo != null) try { fo.close(); } catch (IOException ioe) {} } } } /** * @since 0.9.10 */ private void startListener() throws IOException { listener = new ServerSocket(Integer.parseInt(props.getProperty(PROP_BOB_PORT)), 10, InetAddress.getByName(props.getProperty(PROP_BOB_HOST))); listener.setSoTimeout(500); // .5 sec } /** * @since 0.9.10 */ private void startThread() { I2PAppThread t = new I2PAppThread(this, "BOBListener"); t.start(); _runner = t; } /** * @since 0.9.10 */ public void run() { if (listener == null) return; changeState(RUNNING); _log.info("BOB is now running."); if (_mgr != null) _mgr.register(this); int i = 0; boolean g = false; spin.set(true); try { Socket server = null; while (spin.get()) { //DoCMDS connection; try { server = listener.accept(); server.setKeepAlive(true); g = true; } catch (ConnectException ce) { g = false; } catch (SocketTimeoutException ste) { g = false; } if (g) { DoCMDS conn_c = new DoCMDS(spin, lock, server, props, database, _log); Thread t = new I2PAppThread(conn_c); t.setName("BOB.DoCMDS " + i); t.start(); i++; } } changeState(STOPPING); } catch (Exception e) { if (spin.get()) _log.error("Unexpected error while listening for connections", e); else e = null; changeState(STOPPING, e); } finally { _log.info("BOB is now shutting down..."); // Clean up everything. try { listener.close(); } catch (Exception ex) { // nop } // Find all our "BOB.DoCMDS" threads, wait for them to be finished. // We could order them to stop, but that could cause nasty issues in the locks. visitAllThreads(); database.getReadLock(); NamedDB nickinfo; try { for (Object ndb : database.values()) { nickinfo = (NamedDB) ndb; nickinfo.getReadLock(); boolean released = false; try { if (nickinfo.get(P_RUNNING).equals(Boolean.TRUE) && nickinfo.get(P_STOPPING).equals(Boolean.FALSE) && nickinfo.get(P_STARTING).equals(Boolean.FALSE)) { nickinfo.releaseReadLock(); released = true; nickinfo.getWriteLock(); try { nickinfo.add(P_STOPPING, Boolean.TRUE); } finally { nickinfo.releaseWriteLock(); } } } finally { if (!released) nickinfo.releaseReadLock(); } } } finally { database.releaseReadLock(); } changeState(STOPPED); _log.info("BOB is now stopped."); } } /** * Find the root thread group, * then find all theads with certain names and wait for them all to be dead. * */ private static void visitAllThreads() { ThreadGroup root = Thread.currentThread().getThreadGroup().getParent(); while (root.getParent() != null) { root = root.getParent(); } // Visit each thread group waitjoin(root, 0, root.getName()); } private static void waitjoin(ThreadGroup group, int level, String tn) { // Get threads in `group' int numThreads = group.activeCount(); Thread[] threads = new Thread[numThreads * 2]; numThreads = group.enumerate(threads, false); // Enumerate each thread in `group' and wait for it to stop if it is one of ours. for (int i = 0; i < numThreads; i++) { // Get thread Thread thread = threads[i]; if (thread.getName().startsWith("BOB.DoCMDS ")) { try { if (thread.isAlive()) { try { thread.join(); } catch (InterruptedException ex) { } } } catch (SecurityException se) { //nop } } } // Get thread subgroups of `group' int numGroups = group.activeGroupCount(); ThreadGroup[] groups = new ThreadGroup[numGroups * 2]; numGroups = group.enumerate(groups, false); // Recursively visit each subgroup for (int i = 0; i < numGroups; i++) { waitjoin(groups[i], level + 1, groups[i].getName()); } } ////// begin ClientApp interface /** * @since 0.9.10 */ @Override public void startup() throws IOException { if (_state != INITIALIZED) return; changeState(STARTING); try { startListener(); } catch (IOException e) { _log.error("Error starting BOB on" + props.getProperty(PROP_BOB_HOST) + ":" + props.getProperty(PROP_BOB_PORT), e); changeState(START_FAILED, e); throw e; } startThread(); } /** * @since 0.9.10 */ @Override public void shutdown(String[] args) { if (_state != RUNNING) return; changeState(STOPPING); spin.set(false); if (_runner != null) _runner.interrupt(); else changeState(STOPPED); } /** * @since 0.9.10 */ @Override public ClientAppState getState() { return _state; } /** * @since 0.9.10 */ @Override public String getName() { return "BOB"; } /** * @since 0.9.10 */ @Override public String getDisplayName() { return "BOB " + Arrays.toString(_args); } ////// end ClientApp interface ////// begin ClientApp helpers /** * @since 0.9.10 */ private void changeState(ClientAppState state) { changeState(state, null); } /** * @since 0.9.10 */ private synchronized void changeState(ClientAppState state, Exception e) { _state = state; if (_mgr != null) _mgr.notify(this, state, null, e); } ////// end ClientApp helpers }