commit 64c9fae2b7f4dfa70db19e7ed1d7e187cd9ff09c
Author: Damian Johnson <atagar(a)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