[tor-commits] [arm/release] Optional detached startup

atagar at torproject.org atagar at torproject.org
Sun Jul 17 06:08:27 UTC 2011


commit 64c9fae2b7f4dfa70db19e7ed1d7e187cd9ff09c
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Jun 19 13:10:30 2011 -0700

    Optional detached startup
    
    This is adding a config option that allows us to run arm when there's no Tor
    instance to connect to. This is a prerequisite for both a relay setup wizard
    and arbitrary reattachability.
    
    Arm was written with the expectation that there was a controller instance
    while starting up. This commit fixes most of the obvious crashing issues, but
    this is still gonna take some work to address instability for detached
    startups.
---
 armrc.sample                       |    4 ++++
 src/cli/configPanel.py             |   10 +++++++---
 src/cli/connections/connPanel.py   |    2 +-
 src/cli/controller.py              |    2 +-
 src/cli/graphing/bandwidthStats.py |    2 +-
 src/cli/graphing/connStats.py      |    2 +-
 src/cli/headerPanel.py             |    4 ++--
 src/cli/torrcPanel.py              |    2 +-
 src/starter.py                     |    7 ++++---
 src/util/torConfig.py              |   22 +++++++++++++---------
 src/util/torTools.py               |   13 +++++++------
 11 files changed, 42 insertions(+), 28 deletions(-)

diff --git a/armrc.sample b/armrc.sample
index 54e3f7b..bfe8976 100644
--- a/armrc.sample
+++ b/armrc.sample
@@ -56,6 +56,10 @@ features.redrawRate 5
 # Confirms promt to confirm when quiting if true
 features.confirmQuit true
 
+# Allows arm to start when there's no running tor instance if true, otherwise
+# we terminate right away.
+features.allowDetachedStartup false
+
 # Paremters for the log panel
 # ---------------------------
 # showDateDividers
diff --git a/src/cli/configPanel.py b/src/cli/configPanel.py
index fedf1f7..913bc39 100644
--- a/src/cli/configPanel.py
+++ b/src/cli/configPanel.py
@@ -198,9 +198,12 @@ class ConfigPanel(panel.Panel):
     self.showAll = False
     
     if self.configType == State.TOR:
-      conn = torTools.getConn()
+      conn, configOptionLines = torTools.getConn(), []
       customOptions = torConfig.getCustomOptions()
-      configOptionLines = conn.getInfo("config/names", "").strip().split("\n")
+      configOptionQuery = conn.getInfo("config/names")
+      
+      if configOptionQuery:
+        configOptionLines = configOptionQuery.strip().split("\n")
       
       for line in configOptionLines:
         # lines are of the form "<option> <type>[ <documentation>]", like:
@@ -484,7 +487,8 @@ class ConfigPanel(panel.Panel):
       cursorSelection = self.getSelection()
       isScrollbarVisible = len(self._getConfigOptions()) > height - detailPanelHeight - 1
       
-      self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, isScrollbarVisible)
+      if cursorSelection != None:
+        self._drawSelectionPanel(cursorSelection, width, detailPanelHeight, isScrollbarVisible)
     
     # draws the top label
     if self.isTitleVisible():
diff --git a/src/cli/connections/connPanel.py b/src/cli/connections/connPanel.py
index edf5a14..161d6f6 100644
--- a/src/cli/connections/connPanel.py
+++ b/src/cli/connections/connPanel.py
@@ -90,7 +90,7 @@ class ConnectionPanel(panel.Panel, threading.Thread):
       eventType - type of event detected
     """
     
-    self._isTorRunning = eventType == torTools.State.INIT
+    self._isTorRunning = eventType in (torTools.State.INIT, torTools.State.RESET)
     
     if self._isTorRunning: self._haltTime = None
     else: self._haltTime = time.time()
diff --git a/src/cli/controller.py b/src/cli/controller.py
index feb7cf0..1fb52c9 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -435,7 +435,7 @@ def connResetListener(conn, eventType):
     resolver = connections.getResolver("tor")
     resolver.setPaused(eventType == torTools.State.CLOSED)
     
-    if eventType == torTools.State.INIT:
+    if eventType in (torTools.State.INIT, torTools.State.RESET):
       torPid = conn.getMyPid()
       
       if torPid and torPid != resolver.getPid():
diff --git a/src/cli/graphing/bandwidthStats.py b/src/cli/graphing/bandwidthStats.py
index b92a58c..642790d 100644
--- a/src/cli/graphing/bandwidthStats.py
+++ b/src/cli/graphing/bandwidthStats.py
@@ -86,7 +86,7 @@ class BandwidthStats(graphPanel.GraphStats):
     self._titleStats = []     # force reset of title
     self.new_desc_event(None) # updates title params
     
-    if eventType == torTools.State.INIT and self._config["features.graph.bw.accounting.show"]:
+    if eventType in (torTools.State.INIT, torTools.State.RESET) and self._config["features.graph.bw.accounting.show"]:
       self.isAccounting = conn.getInfo('accounting/enabled') == '1'
     
     # redraws to reflect changes (this especially noticeable when we have
diff --git a/src/cli/graphing/connStats.py b/src/cli/graphing/connStats.py
index 7f0dc18..0df5db3 100644
--- a/src/cli/graphing/connStats.py
+++ b/src/cli/graphing/connStats.py
@@ -25,7 +25,7 @@ class ConnStats(graphPanel.GraphStats):
     return graphPanel.GraphStats.clone(self, newCopy)
   
   def resetListener(self, conn, eventType):
-    if eventType == torTools.State.INIT:
+    if eventType in (torTools.State.INIT, torTools.State.RESET):
       self.orPort = conn.getOption("ORPort", "0")
       self.dirPort = conn.getOption("DirPort", "0")
       self.controlPort = conn.getOption("ControlPort", "0")
diff --git a/src/cli/headerPanel.py b/src/cli/headerPanel.py
index 7e0f639..733abad 100644
--- a/src/cli/headerPanel.py
+++ b/src/cli/headerPanel.py
@@ -65,7 +65,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
     self._config = dict(DEFAULT_CONFIG)
     if config: config.update(self._config)
     
-    self._isTorConnected = True
+    self._isTorConnected = torTools.getConn().isAlive()
     self._lastUpdate = -1       # time the content was last revised
     self._halt = False          # terminates thread if true
     self._cond = threading.Condition()  # used for pausing the thread
@@ -405,7 +405,7 @@ class HeaderPanel(panel.Panel, threading.Thread):
       eventType - type of event detected
     """
     
-    if eventType == torTools.State.INIT:
+    if eventType in (torTools.State.INIT, torTools.State.RESET):
       self._isTorConnected = True
       self._haltTime = None
       self._update(True)
diff --git a/src/cli/torrcPanel.py b/src/cli/torrcPanel.py
index f651785..2dc244f 100644
--- a/src/cli/torrcPanel.py
+++ b/src/cli/torrcPanel.py
@@ -53,7 +53,7 @@ class TorrcPanel(panel.Panel):
       eventType - type of event detected
     """
     
-    if eventType == torTools.State.INIT:
+    if eventType in (torTools.State.INIT, torTools.State.RESET):
       try:
         torConfig.getTorrc().load(True)
         self.redraw(True)
diff --git a/src/starter.py b/src/starter.py
index d50c718..104e0e2 100644
--- a/src/starter.py
+++ b/src/starter.py
@@ -40,6 +40,7 @@ CONFIG = {"startup.controlPassword": None,
           "startup.blindModeEnabled": False,
           "startup.events": "N3",
           "startup.dataDirectory": "~/.arm",
+          "features.allowDetachedStartup": False,
           "features.config.descriptions.enabled": True,
           "features.config.descriptions.persist": True,
           "log.configDescriptions.readManPageSuccess": util.log.INFO,
@@ -216,7 +217,7 @@ def _torCtlConnect(controlAddr="127.0.0.1", controlPort=9051, passphrase=None, i
       # again prompting for the user to enter it
       print incorrectPasswordMsg
       return _torCtlConnect(controlAddr, controlPort)
-    else:
+    elif not CONFIG["features.allowDetachedStartup"]:
       print exc
       return None
 
@@ -388,7 +389,7 @@ if __name__ == '__main__':
   authPassword = config.get("startup.controlPassword", CONFIG["startup.controlPassword"])
   incorrectPasswordMsg = "Password found in '%s' was incorrect" % configPath
   conn = _torCtlConnect(controlAddr, controlPort, authPassword, incorrectPasswordMsg)
-  if conn == None: sys.exit(1)
+  if conn == None and not CONFIG["features.allowDetachedStartup"]: sys.exit(1)
   
   # removing references to the controller password so the memory can be freed
   # (unfortunately python does allow for direct access to the memory so this
@@ -410,7 +411,7 @@ if __name__ == '__main__':
   # skewing the startup time results so this isn't counted
   initTime = time.time() - startTime
   controller = util.torTools.getConn()
-  controller.init(conn)
+  if conn: controller.init(conn)
   
   # fetches descriptions for tor's configuration options
   _loadConfigurationDescriptions(pathPrefix)
diff --git a/src/util/torConfig.py b/src/util/torConfig.py
index 14ef4ce..a63eddb 100644
--- a/src/util/torConfig.py
+++ b/src/util/torConfig.py
@@ -178,9 +178,10 @@ def loadOptionDescriptions(loadPath = None, checkVersion = True):
       # Fetches all options available with this tor instance. This isn't
       # vital, and the validOptions are left empty if the call fails.
       conn, validOptions = torTools.getConn(), []
-      configOptionQuery = conn.getInfo("config/names").strip().split("\n")
+      configOptionQuery = conn.getInfo("config/names")
       if configOptionQuery:
-        validOptions = [line[:line.find(" ")].lower() for line in configOptionQuery]
+        for line in configOptionQuery.strip().split("\n"):
+          validOptions.append(line[:line.find(" ")].lower())
       
       optionCount, lastOption, lastArg = 0, None, None
       lastCategory, lastDescription = Category.GENERAL, ""
@@ -343,14 +344,17 @@ def getMultilineParameters():
   # 'Dependent'), and LINELIST_V (aka 'Virtual') types
   global MULTILINE_PARAM
   if MULTILINE_PARAM == None:
-    conn = torTools.getConn()
-    configOptionQuery = conn.getInfo("config/names", "").strip().split("\n")
-    
-    multilineEntries = []
-    for line in configOptionQuery:
-      confOption, confType = line.strip().split(" ", 1)
-      if confType in ("LineList", "Dependant", "Virtual"):
-        multilineEntries.append(confOption)
+    conn, multilineEntries = torTools.getConn(), []
+    
+    configOptionQuery = conn.getInfo("config/names")
+    if configOptionQuery:
+      for line in configOptionQuery.strip().split("\n"):
+        confOption, confType = line.strip().split(" ", 1)
+        if confType in ("LineList", "Dependant", "Virtual"):
+          multilineEntries.append(confOption)
+    else:
+      # unable to query tor connection, so not caching results
+      return ()
     
     MULTILINE_PARAM = multilineEntries
   
diff --git a/src/util/torTools.py b/src/util/torTools.py
index 7ea140b..d1e5e9d 100644
--- a/src/util/torTools.py
+++ b/src/util/torTools.py
@@ -17,9 +17,10 @@ from TorCtl import TorCtl, TorUtil
 from util import enum, log, procTools, sysTools, uiTools
 
 # enums for tor's controller state:
-# INIT - attached to a new controller or restart/sighup signal received
+# INIT - attached to a new controller
+# RESET - received a reset/sighup signal
 # CLOSED - control port closed
-State = enum.Enum("INIT", "CLOSED")
+State = enum.Enum("INIT", "RESET", "CLOSED")
 
 # Addresses of the default directory authorities for tor version 0.2.3.0-alpha
 # (this comes from the dirservers array in src/or/config.c).
@@ -1068,7 +1069,7 @@ class Controller(TorCtl.PostEventListener):
           if myAddress: result = ExitPolicy("reject %s" % myAddress, result)
       else:
         # no ORPort is set so all relaying is disabled
-        result = ExitPolicy("reject *:*")
+        result = ExitPolicy("reject *:*", None)
     
     self.connLock.release()
     
@@ -1469,11 +1470,11 @@ class Controller(TorCtl.PostEventListener):
       if self.isAlive():
         self._isReset = True
         
-        self._status = State.INIT
+        self._status = State.RESET
         self._statusTime = time.time()
         
         if not NO_SPAWN:
-          self._notificationQueue.put(State.INIT)
+          self._notificationQueue.put(State.RESET)
           thread.start_new_thread(self._notifyStatusListeners, ())
       
       self.connLock.release()
@@ -2038,7 +2039,7 @@ class Controller(TorCtl.PostEventListener):
       eventType = self._notificationQueue.get(timeout=0)
       
       # checks that the notice is accurate for our current state
-      if self.isAlive() != (eventType == State.INIT):
+      if self.isAlive() != (eventType in (State.INIT, State.RESET)):
         eventType = None
     except Queue.Empty:
       eventType = None





More information about the tor-commits mailing list