commit 64c9fae2b7f4dfa70db19e7ed1d7e187cd9ff09c Author: Damian Johnson atagar@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
tor-commits@lists.torproject.org