[tor-commits] [stem/master] When launching tor avoid writing a torrc to disk if able

atagar at torproject.org atagar at torproject.org
Mon Feb 23 03:07:13 UTC 2015


commit a2d13131003b0d1abf22d9e5475a762c3f4d4679
Author: Damian Johnson <atagar at torproject.org>
Date:   Sun Feb 22 18:44:31 2015 -0800

    When launching tor avoid writing a torrc to disk if able
    
    Tor now supports providing the torrc via stdin, so taking advantage of that
    when we can...
    
      https://trac.torproject.org/projects/tor/ticket/13865
---
 docs/change_log.rst   |    1 +
 stem/process.py       |   64 +++++++++++++++++++++++++++++++++----------------
 test/integ/process.py |   43 +++++++++++++++++++++++++++++----
 3 files changed, 83 insertions(+), 25 deletions(-)

diff --git a/docs/change_log.rst b/docs/change_log.rst
index 58fa34f..af883ce 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -51,6 +51,7 @@ conversion (:trac:`14075`).
 
  * **Controller**
 
+  * :func:`~stem.process.launch_tor_with_config` avoids writing a temporary torrc to disk if able (:trac:`13865`)
   * The 'strict' argument of :func:`~stem.exit_policy.ExitPolicy.can_exit_to` didn't behave as documented (:trac:`14314`)
 
  * **Descriptors**
diff --git a/stem/process.py b/stem/process.py
index be69810..b8b1f92 100644
--- a/stem/process.py
+++ b/stem/process.py
@@ -25,13 +25,15 @@ import subprocess
 import tempfile
 
 import stem.prereq
+import stem.util.str_tools
 import stem.util.system
+import stem.version
 
 NO_TORRC = '<no torrc>'
 DEFAULT_INIT_TIMEOUT = 90
 
 
-def launch_tor(tor_cmd = 'tor', args = None, torrc_path = None, completion_percent = 100, init_msg_handler = None, timeout = DEFAULT_INIT_TIMEOUT, take_ownership = False):
+def launch_tor(tor_cmd = 'tor', args = None, torrc_path = None, completion_percent = 100, init_msg_handler = None, timeout = DEFAULT_INIT_TIMEOUT, take_ownership = False, stdin = None):
   """
   Initializes a tor process. This blocks until initialization completes or we
   error out.
@@ -60,6 +62,7 @@ def launch_tor(tor_cmd = 'tor', args = None, torrc_path = None, completion_perce
   :param bool take_ownership: asserts ownership over the tor process so it
     aborts if this python process terminates or a :class:`~stem.control.Controller`
     we establish to it disconnects
+  :param str stdin: content to provide on stdin
 
   :returns: **subprocess.Popen** instance for the tor subprocess
 
@@ -102,7 +105,11 @@ def launch_tor(tor_cmd = 'tor', args = None, torrc_path = None, completion_perce
   if take_ownership:
     runtime_args += ['__OwningControllerProcess', str(os.getpid())]
 
-  tor_process = subprocess.Popen(runtime_args, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
+  tor_process = subprocess.Popen(runtime_args, stdout = subprocess.PIPE, stdin = subprocess.PIPE, stderr = subprocess.PIPE)
+
+  if stdin:
+    tor_process.stdin.write(stem.util.str_tools._to_bytes(stdin))
+    tor_process.stdin.close()
 
   if timeout:
     def timeout_handler(signum, frame):
@@ -208,6 +215,14 @@ def launch_tor_with_config(config, tor_cmd = 'tor', completion_percent = 100, in
     timeout without success
   """
 
+  # TODO: Drop this version check when tor 0.2.6.3 or higher is the only game
+  # in town.
+
+  try:
+    use_stdin = stem.version.get_system_tor_version(tor_cmd) >= stem.version.Requirement.TORRC_VIA_STDIN
+  except IOError:
+    use_stdin = False
+
   # we need to be sure that we're logging to stdout to figure out when we're
   # done bootstrapping
 
@@ -227,24 +242,31 @@ def launch_tor_with_config(config, tor_cmd = 'tor', completion_percent = 100, in
     if not has_stdout:
       config['Log'].append('NOTICE stdout')
 
-  torrc_descriptor, torrc_path = tempfile.mkstemp(prefix = 'torrc-', text = True)
+  config_str = ''
+
+  for key, values in list(config.items()):
+    if isinstance(values, str):
+      config_str += '%s %s\n' % (key, values)
+    else:
+      for value in values:
+        config_str += '%s %s\n' % (key, value)
+
+  if use_stdin:
+    return launch_tor(tor_cmd, ['-f', '-'], None, completion_percent, init_msg_handler, timeout, take_ownership, stdin = config_str)
+  else:
+    torrc_descriptor, torrc_path = tempfile.mkstemp(prefix = 'torrc-', text = True)
 
-  try:
-    with open(torrc_path, 'w') as torrc_file:
-      for key, values in list(config.items()):
-        if isinstance(values, str):
-          torrc_file.write('%s %s\n' % (key, values))
-        else:
-          for value in values:
-            torrc_file.write('%s %s\n' % (key, value))
-
-    # prevents tor from erroring out due to a missing torrc if it gets a sighup
-    args = ['__ReloadTorrcOnSIGHUP', '0']
-
-    return launch_tor(tor_cmd, args, torrc_path, completion_percent, init_msg_handler, timeout, take_ownership)
-  finally:
     try:
-      os.close(torrc_descriptor)
-      os.remove(torrc_path)
-    except:
-      pass
+      with open(torrc_path, 'w') as torrc_file:
+        torrc_file.write(config_str)
+
+      # prevents tor from erroring out due to a missing torrc if it gets a sighup
+      args = ['__ReloadTorrcOnSIGHUP', '0']
+
+      return launch_tor(tor_cmd, args, torrc_path, completion_percent, init_msg_handler, timeout, take_ownership)
+    finally:
+      try:
+        os.close(torrc_descriptor)
+        os.remove(torrc_path)
+      except:
+        pass
diff --git a/test/integ/process.py b/test/integ/process.py
index d889272..e393a25 100644
--- a/test/integ/process.py
+++ b/test/integ/process.py
@@ -29,9 +29,9 @@ from test.runner import (
 
 try:
   # added in python 3.3
-  from unittest.mock import patch
+  from unittest.mock import patch, Mock
 except ImportError:
-  from mock import patch
+  from mock import patch, Mock
 
 BASIC_RELAY_TORRC = """\
 ORPort 6000
@@ -253,9 +253,10 @@ class TestProcess(unittest.TestCase):
     self.assertTrue('Configuration was valid' in output)
 
   @only_run_once
-  def test_launch_tor_with_config(self):
+  @patch('stem.version.get_system_tor_version', Mock(return_value = stem.version.Version('0.0.0.1')))
+  def test_launch_tor_with_config_via_file(self):
     """
-    Exercises launch_tor_with_config.
+    Exercises launch_tor_with_config when we write a torrc to disk.
     """
 
     # Launch tor without a torrc, but with a control port. Confirms that this
@@ -289,6 +290,40 @@ class TestProcess(unittest.TestCase):
       tor_process.wait()
 
   @only_run_once
+  @require_version(stem.version.Requirement.TORRC_VIA_STDIN)
+  def test_launch_tor_with_config_via_stdin(self):
+    """
+    Exercises launch_tor_with_config when we provide our torrc via stdin.
+    """
+
+    runner = test.runner.get_runner()
+    tor_process = stem.process.launch_tor_with_config(
+      tor_cmd = runner.get_tor_command(),
+      config = {
+        'SocksPort': '2777',
+        'ControlPort': '2778',
+        'DataDirectory': self.data_directory,
+      },
+      completion_percent = 5
+    )
+
+    control_socket = None
+    try:
+      control_socket = stem.socket.ControlPort(port = 2778)
+      stem.connection.authenticate(control_socket, chroot_path = runner.get_chroot())
+
+      # exercises the socket
+      control_socket.send('GETCONF ControlPort')
+      getconf_response = control_socket.recv()
+      self.assertEqual('ControlPort=2778', str(getconf_response))
+    finally:
+      if control_socket:
+        control_socket.close()
+
+      tor_process.kill()
+      tor_process.wait()
+
+  @only_run_once
   def test_with_invalid_config(self):
     """
     Spawn a tor process with a configuration that should make it dead on arrival.



More information about the tor-commits mailing list