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

commit a2d13131003b0d1abf22d9e5475a762c3f4d4679 Author: Damian Johnson <atagar@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.
participants (1)
-
atagar@torproject.org