More work on #888 (new IPC path).
First trying the legacy path and then using the new one works fine now, but the permissions are still wrong.
This commit is contained in:
parent
74d7997a67
commit
78cb0eaf85
@ -20,6 +20,7 @@
|
|||||||
"""Utilities for IPC with existing instances."""
|
"""Utilities for IPC with existing instances."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
import getpass
|
import getpass
|
||||||
@ -39,11 +40,8 @@ READ_TIMEOUT = 5000
|
|||||||
PROTOCOL_VERSION = 1
|
PROTOCOL_VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
def _get_socketname(basedir, runtime_dir, legacy=True, user=None):
|
def _get_socketname(basedir, legacy=False):
|
||||||
"""Get a socketname to use."""
|
"""Get a socketname to use."""
|
||||||
if user is None:
|
|
||||||
user = getpass.getuser()
|
|
||||||
|
|
||||||
if basedir is None:
|
if basedir is None:
|
||||||
basedir_md5 = None
|
basedir_md5 = None
|
||||||
else:
|
else:
|
||||||
@ -51,15 +49,22 @@ def _get_socketname(basedir, runtime_dir, legacy=True, user=None):
|
|||||||
basedir_md5 = md5.hexdigest()
|
basedir_md5 = md5.hexdigest()
|
||||||
|
|
||||||
if legacy or os.name == 'nt':
|
if legacy or os.name == 'nt':
|
||||||
parts = ['qutebrowser', user]
|
parts = ['qutebrowser', getpass.getuser()]
|
||||||
if basedir_md5 is not None:
|
if basedir_md5 is not None:
|
||||||
parts.append(basedir_md5)
|
parts.append(basedir_md5)
|
||||||
return '-'.join(parts)
|
return '-'.join(parts)
|
||||||
else:
|
|
||||||
|
if sys.platform.startswith('linux'):
|
||||||
|
target_dir = standarddir.runtime()
|
||||||
parts = ['qutebrowser-ipc']
|
parts = ['qutebrowser-ipc']
|
||||||
if basedir_md5 is not None:
|
else: # pragma: no cover
|
||||||
parts.append(basedir_md5)
|
# OS X or other Unix
|
||||||
return os.path.join(runtime_dir, '-'.join(parts))
|
parts = ['ipc']
|
||||||
|
target_dir = standarddir.temp()
|
||||||
|
|
||||||
|
if basedir_md5 is not None:
|
||||||
|
parts.append(basedir_md5)
|
||||||
|
return os.path.join(target_dir, '-'.join(parts))
|
||||||
|
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
@ -186,7 +191,7 @@ class IPCServer(QObject):
|
|||||||
if self._socket is None:
|
if self._socket is None:
|
||||||
# Sometimes this gets called from stale sockets.
|
# Sometimes this gets called from stale sockets.
|
||||||
msg = "In on_error with None socket!"
|
msg = "In on_error with None socket!"
|
||||||
if os.name == 'nt': # pragma: no coverage
|
if os.name == 'nt': # pragma: no cover
|
||||||
# This happens a lot on Windows, so we ignore it there.
|
# This happens a lot on Windows, so we ignore it there.
|
||||||
log.ipc.debug(msg)
|
log.ipc.debug(msg)
|
||||||
else:
|
else:
|
||||||
@ -319,7 +324,28 @@ class IPCServer(QObject):
|
|||||||
self._remove_server()
|
self._remove_server()
|
||||||
|
|
||||||
|
|
||||||
def send_to_running_instance(socketname, command, *, socket=None):
|
def _has_legacy_server(name):
|
||||||
|
"""Check if there is a legacy server.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: The name to try to connect to.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
True if there is a server with the given name, False otherwise.
|
||||||
|
"""
|
||||||
|
socket = QLocalSocket()
|
||||||
|
log.ipc.debug("Trying to connect to {}".format(name))
|
||||||
|
socket.connectToServer(name)
|
||||||
|
if socket.error() != QLocalSocket.ServerNotFoundError:
|
||||||
|
return True
|
||||||
|
socket.disconnectFromServer()
|
||||||
|
if socket.state() != QLocalSocket.UnconnectedState:
|
||||||
|
socket.waitForDisconnected(100)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def send_to_running_instance(socketname, command, *, legacy_name=None,
|
||||||
|
socket=None):
|
||||||
"""Try to send a commandline to a running instance.
|
"""Try to send a commandline to a running instance.
|
||||||
|
|
||||||
Blocks for CONNECT_TIMEOUT ms.
|
Blocks for CONNECT_TIMEOUT ms.
|
||||||
@ -328,14 +354,23 @@ def send_to_running_instance(socketname, command, *, socket=None):
|
|||||||
socketname: The name which should be used for the socket.
|
socketname: The name which should be used for the socket.
|
||||||
command: The command to send to the running instance.
|
command: The command to send to the running instance.
|
||||||
socket: The socket to read data from, or None.
|
socket: The socket to read data from, or None.
|
||||||
|
legacy_name: The legacy name to first try to connect to.
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
True if connecting was successful, False if no connection was made.
|
True if connecting was successful, False if no connection was made.
|
||||||
"""
|
"""
|
||||||
if socket is None:
|
if socket is None:
|
||||||
socket = QLocalSocket()
|
socket = QLocalSocket()
|
||||||
log.ipc.debug("Connecting to {}".format(socketname))
|
|
||||||
socket.connectToServer(socketname)
|
if (legacy_name is not None and
|
||||||
|
_has_legacy_server(legacy_name)):
|
||||||
|
name_to_use = legacy_name
|
||||||
|
else:
|
||||||
|
name_to_use = socketname
|
||||||
|
|
||||||
|
log.ipc.debug("Connecting to {}".format(name_to_use))
|
||||||
|
socket.connectToServer(name_to_use)
|
||||||
|
|
||||||
connected = socket.waitForConnected(100)
|
connected = socket.waitForConnected(100)
|
||||||
if connected:
|
if connected:
|
||||||
log.ipc.info("Opening in existing instance")
|
log.ipc.info("Opening in existing instance")
|
||||||
@ -386,10 +421,12 @@ def send_or_listen(args):
|
|||||||
The IPCServer instance if no running instance was detected.
|
The IPCServer instance if no running instance was detected.
|
||||||
None if an instance was running and received our request.
|
None if an instance was running and received our request.
|
||||||
"""
|
"""
|
||||||
socketname = _get_socketname(args.basedir, standarddir.runtime())
|
socketname = _get_socketname(args.basedir)
|
||||||
|
legacy_socketname = _get_socketname(args.basedir, legacy=True)
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
sent = send_to_running_instance(socketname, args.command)
|
sent = send_to_running_instance(socketname, args.command,
|
||||||
|
legacy_name=legacy_socketname)
|
||||||
if sent:
|
if sent:
|
||||||
return None
|
return None
|
||||||
log.init.debug("Starting IPC server...")
|
log.init.debug("Starting IPC server...")
|
||||||
@ -401,7 +438,8 @@ def send_or_listen(args):
|
|||||||
# This could be a race condition...
|
# This could be a race condition...
|
||||||
log.init.debug("Got AddressInUseError, trying again.")
|
log.init.debug("Got AddressInUseError, trying again.")
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
sent = send_to_running_instance(socketname, args.command)
|
sent = send_to_running_instance(socketname, args.command,
|
||||||
|
legacy_name=legacy_socketname)
|
||||||
if sent:
|
if sent:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
@ -85,6 +85,7 @@ def _from_args(typ, args):
|
|||||||
override: boolean, if the user did override the path
|
override: boolean, if the user did override the path
|
||||||
path: The overridden path, or None to turn off storage.
|
path: The overridden path, or None to turn off storage.
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=too-many-return-statements
|
||||||
typ_to_argparse_arg = {
|
typ_to_argparse_arg = {
|
||||||
QStandardPaths.ConfigLocation: 'confdir',
|
QStandardPaths.ConfigLocation: 'confdir',
|
||||||
QStandardPaths.DataLocation: 'datadir',
|
QStandardPaths.DataLocation: 'datadir',
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
"""Tests for qutebrowser.misc.ipc."""
|
"""Tests for qutebrowser.misc.ipc."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
import getpass
|
import getpass
|
||||||
import collections
|
import collections
|
||||||
import logging
|
import logging
|
||||||
@ -73,7 +75,7 @@ def qlocalsocket(qapp):
|
|||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def fake_runtime_dir(monkeypatch, tmpdir):
|
def fake_runtime_dir(monkeypatch, tmpdir):
|
||||||
monkeypatch.setenv('XDG_RUNTIME_DIR', str(tmpdir))
|
monkeypatch.setenv('XDG_RUNTIME_DIR', str(tmpdir))
|
||||||
return str(tmpdir)
|
return tmpdir
|
||||||
|
|
||||||
|
|
||||||
class FakeSocket(QObject):
|
class FakeSocket(QObject):
|
||||||
@ -165,21 +167,61 @@ def test_getpass_getuser():
|
|||||||
assert getpass.getuser()
|
assert getpass.getuser()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('username, basedir, legacy, expected', [
|
class TestSocketName:
|
||||||
('florian', None, True, 'qutebrowser-florian'),
|
|
||||||
('florian', '/x', True,
|
|
||||||
'qutebrowser-florian-cc8755609ad61864910f145119713de9'),
|
|
||||||
(None, None, True, 'qutebrowser-{}'.format(getpass.getuser())),
|
|
||||||
|
|
||||||
('florian', None, False, '/runtimedir/qutebrowser-ipc'),
|
MD5 = 'cc8755609ad61864910f145119713de9' # The MD5 of 'x'
|
||||||
('florian', '/x', False,
|
|
||||||
'/runtimedir/qutebrowser-ipc-cc8755609ad61864910f145119713de9'),
|
LEGACY_TESTS = [
|
||||||
(None, None, False, '/runtimedir/qutebrowser-ipc'),
|
(None, 'qutebrowser-{}'.format(getpass.getuser())),
|
||||||
])
|
('/x', 'qutebrowser-{}-{}'.format(getpass.getuser(), MD5)),
|
||||||
def test_get_socketname(username, basedir, legacy, expected):
|
]
|
||||||
socketname = ipc._get_socketname(basedir, '/runtimedir', legacy=legacy,
|
|
||||||
user=username)
|
@pytest.mark.parametrize('basedir, expected', LEGACY_TESTS)
|
||||||
assert socketname == expected
|
def test_legacy(self, basedir, expected):
|
||||||
|
socketname = ipc._get_socketname(basedir, legacy=True)
|
||||||
|
assert socketname == expected
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('basedir, expected', LEGACY_TESTS)
|
||||||
|
@pytest.mark.windows
|
||||||
|
def test_windows(self, basedir, expected):
|
||||||
|
socketname = ipc._get_socketname(basedir)
|
||||||
|
assert socketname == expected
|
||||||
|
|
||||||
|
@pytest.mark.osx
|
||||||
|
@pytest.mark.parametrize('basedir, expected', [
|
||||||
|
(None, 'ipc'),
|
||||||
|
('/x', 'ipc-{}'.format(MD5)),
|
||||||
|
])
|
||||||
|
def test_os_x(self, basedir, expected):
|
||||||
|
socketname = ipc._get_socketname(basedir)
|
||||||
|
parts = os.path.split(socketname)
|
||||||
|
assert parts[-2] == 'qutebrowser_test'
|
||||||
|
assert parts[-1] == expected
|
||||||
|
|
||||||
|
@pytest.mark.linux
|
||||||
|
@pytest.mark.parametrize('basedir, expected', [
|
||||||
|
(None, 'qutebrowser-ipc'),
|
||||||
|
('/x', 'qutebrowser-ipc-{}'.format(MD5)),
|
||||||
|
])
|
||||||
|
def test_linux(self, basedir, fake_runtime_dir, expected):
|
||||||
|
socketname = ipc._get_socketname(basedir)
|
||||||
|
expected_path = str(fake_runtime_dir / 'qutebrowser_test' / expected)
|
||||||
|
assert socketname == expected_path
|
||||||
|
|
||||||
|
def test_other_unix(self):
|
||||||
|
"""Fake test for POSIX systems which aren't Linux/OS X.
|
||||||
|
|
||||||
|
We probably would adjust the code first to make it work on that
|
||||||
|
platform.
|
||||||
|
"""
|
||||||
|
if os.name == 'nt':
|
||||||
|
pass
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
pass
|
||||||
|
elif sys.platform.startswith('linux'):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise Exception("Unexpected platform!")
|
||||||
|
|
||||||
|
|
||||||
class TestExceptions:
|
class TestExceptions:
|
||||||
@ -523,6 +565,15 @@ class TestSendOrListen:
|
|||||||
setattr(m, attr, getattr(QLocalSocket, attr))
|
setattr(m, attr, getattr(QLocalSocket, attr))
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
@pytest.yield_fixture
|
||||||
|
def legacy_server(self, args, tmpdir, monkeypatch):
|
||||||
|
monkeypatch.setenv('TMPDIR', str(tmpdir))
|
||||||
|
legacy_name = ipc._get_socketname(args.basedir, legacy=True)
|
||||||
|
legacy_server = ipc.IPCServer(legacy_name)
|
||||||
|
legacy_server.listen()
|
||||||
|
yield legacy_server
|
||||||
|
legacy_server.shutdown()
|
||||||
|
|
||||||
@pytest.mark.posix # Flaky on Windows
|
@pytest.mark.posix # Flaky on Windows
|
||||||
def test_normal_connection(self, caplog, qtbot, args):
|
def test_normal_connection(self, caplog, qtbot, args):
|
||||||
ret_server = ipc.send_or_listen(args)
|
ret_server = ipc.send_or_listen(args)
|
||||||
@ -538,6 +589,21 @@ class TestSendOrListen:
|
|||||||
assert ret_client is None
|
assert ret_client is None
|
||||||
ret_server.shutdown()
|
ret_server.shutdown()
|
||||||
|
|
||||||
|
@pytest.mark.posix # Unneeded on Windows
|
||||||
|
def test_legacy_name(self, caplog, qtbot, args, legacy_server):
|
||||||
|
with qtbot.waitSignal(legacy_server.got_args, raising=True):
|
||||||
|
ret = ipc.send_or_listen(args)
|
||||||
|
assert ret is None
|
||||||
|
msgs = [e.message for e in caplog.records()]
|
||||||
|
assert "Connecting to {}".format(legacy_server._socketname) in msgs
|
||||||
|
|
||||||
|
@pytest.mark.posix # Unneeded on Windows
|
||||||
|
def test_correct_socket_name(self, args):
|
||||||
|
server = ipc.send_or_listen(args)
|
||||||
|
expected_dir = ipc._get_socketname(args.basedir)
|
||||||
|
assert '/' in expected_dir
|
||||||
|
assert server._socketname == expected_dir
|
||||||
|
|
||||||
def test_address_in_use_ok(self, qlocalserver_mock, qlocalsocket_mock,
|
def test_address_in_use_ok(self, qlocalserver_mock, qlocalsocket_mock,
|
||||||
stubs, caplog, args):
|
stubs, caplog, args):
|
||||||
"""Test the following scenario:
|
"""Test the following scenario:
|
||||||
@ -555,7 +621,9 @@ class TestSendOrListen:
|
|||||||
|
|
||||||
qlocalsocket_mock().waitForConnected.side_effect = [False, True]
|
qlocalsocket_mock().waitForConnected.side_effect = [False, True]
|
||||||
qlocalsocket_mock().error.side_effect = [
|
qlocalsocket_mock().error.side_effect = [
|
||||||
|
QLocalSocket.ServerNotFoundError, # legacy name
|
||||||
QLocalSocket.ServerNotFoundError,
|
QLocalSocket.ServerNotFoundError,
|
||||||
|
QLocalSocket.ServerNotFoundError, # legacy name
|
||||||
QLocalSocket.UnknownSocketError,
|
QLocalSocket.UnknownSocketError,
|
||||||
QLocalSocket.UnknownSocketError, # error() gets called twice
|
QLocalSocket.UnknownSocketError, # error() gets called twice
|
||||||
]
|
]
|
||||||
@ -591,8 +659,10 @@ class TestSendOrListen:
|
|||||||
# If it fails, that's the "not sent" case above.
|
# If it fails, that's the "not sent" case above.
|
||||||
qlocalsocket_mock().waitForConnected.side_effect = [False, has_error]
|
qlocalsocket_mock().waitForConnected.side_effect = [False, has_error]
|
||||||
qlocalsocket_mock().error.side_effect = [
|
qlocalsocket_mock().error.side_effect = [
|
||||||
|
QLocalSocket.ServerNotFoundError, # legacy name
|
||||||
QLocalSocket.ServerNotFoundError,
|
QLocalSocket.ServerNotFoundError,
|
||||||
QLocalSocket.ServerNotFoundError,
|
QLocalSocket.ServerNotFoundError,
|
||||||
|
QLocalSocket.ServerNotFoundError, # legacy name
|
||||||
QLocalSocket.ConnectionRefusedError,
|
QLocalSocket.ConnectionRefusedError,
|
||||||
QLocalSocket.ConnectionRefusedError, # error() gets called twice
|
QLocalSocket.ConnectionRefusedError, # error() gets called twice
|
||||||
]
|
]
|
||||||
@ -641,13 +711,27 @@ class TestSendOrListen:
|
|||||||
assert records[0].msg == '\n'.join(error_msgs)
|
assert records[0].msg == '\n'.join(error_msgs)
|
||||||
|
|
||||||
|
|
||||||
def test_long_username(fake_runtime_dir):
|
@pytest.mark.windows
|
||||||
|
@pytest.mark.osx
|
||||||
|
def test_long_username(monkeypatch):
|
||||||
"""See https://github.com/The-Compiler/qutebrowser/issues/888."""
|
"""See https://github.com/The-Compiler/qutebrowser/issues/888."""
|
||||||
name = ipc._get_socketname(basedir='/foo',
|
username = 'alexandercogneau'
|
||||||
runtime_dir=fake_runtime_dir,
|
monkeypatch.setattr('qutebrowser.misc.ipc.standarddir.getpass.getuser',
|
||||||
user='alexandercogneau')
|
lambda: username)
|
||||||
|
name = ipc._get_socketname(basedir='/foo')
|
||||||
server = ipc.IPCServer(name)
|
server = ipc.IPCServer(name)
|
||||||
|
assert username in server._socketname
|
||||||
try:
|
try:
|
||||||
server.listen()
|
server.listen()
|
||||||
finally:
|
finally:
|
||||||
server.shutdown()
|
server.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def test_connect_inexistant(qlocalsocket):
|
||||||
|
"""Make sure connecting to an inexistant server fails immediately.
|
||||||
|
|
||||||
|
If this test fails, our connection logic checking for the old naming scheme
|
||||||
|
would not work properly.
|
||||||
|
"""
|
||||||
|
qlocalsocket.connectToServer('qutebrowser-test-inexistent')
|
||||||
|
assert qlocalsocket.error() == QLocalSocket.ServerNotFoundError
|
||||||
|
@ -232,7 +232,7 @@ class TestArguments:
|
|||||||
lambda _typ: str(tmpdir))
|
lambda _typ: str(tmpdir))
|
||||||
args = types.SimpleNamespace(confdir=None, cachedir=None, datadir=None)
|
args = types.SimpleNamespace(confdir=None, cachedir=None, datadir=None)
|
||||||
standarddir.init(args)
|
standarddir.init(args)
|
||||||
assert standarddir.runtime() == str(tmpdir)
|
assert standarddir.runtime() == str(tmpdir / 'qutebrowser_test')
|
||||||
|
|
||||||
@pytest.mark.parametrize('typ', ['config', 'data', 'cache', 'download',
|
@pytest.mark.parametrize('typ', ['config', 'data', 'cache', 'download',
|
||||||
'runtime'])
|
'runtime'])
|
||||||
|
Loading…
Reference in New Issue
Block a user