Skip to content

Commit 41bb280

Browse files
committed
Add support for Unix file descriptors
1 parent cc407c8 commit 41bb280

File tree

5 files changed

+151
-8
lines changed

5 files changed

+151
-8
lines changed

pydbus/proxy_method.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from .generic import bound_method
33
from .identifier import filter_identifier
44
from .timeout import timeout_to_glib
5+
from . import unixfd
56

67
try:
78
from inspect import Signature, Parameter
@@ -69,10 +70,23 @@ def __call__(self, instance, *args, **kwargs):
6970
raise TypeError(self.__qualname__ + " got an unexpected keyword argument '{}'".format(kwarg))
7071
timeout = kwargs.get("timeout", None)
7172

72-
ret = instance._bus.con.call_sync(
73-
instance._bus_name, instance._path,
74-
self._iface_name, self.__name__, GLib.Variant(self._sinargs, args), GLib.VariantType.new(self._soutargs),
75-
0, timeout_to_glib(timeout), None).unpack()
73+
if unixfd.is_supported(instance._bus.con):
74+
fd_list = unixfd.make_fd_list(
75+
args,
76+
[arg[1] for arg in self._inargs])
77+
ret, fd_list = instance._bus.con.call_with_unix_fd_list_sync(
78+
instance._bus_name, instance._path,
79+
self._iface_name, self.__name__, GLib.Variant(self._sinargs, args), GLib.VariantType.new(self._soutargs),
80+
0, timeout_to_glib(timeout), fd_list, None)
81+
ret = unixfd.extract(
82+
ret.unpack(),
83+
self._outargs,
84+
fd_list)
85+
else:
86+
ret = instance._bus.con.call_sync(
87+
instance._bus_name, instance._path,
88+
self._iface_name, self.__name__, GLib.Variant(self._sinargs, args), GLib.VariantType.new(self._soutargs),
89+
0, timeout_to_glib(timeout), None)
7690

7791
if len(self._outargs) == 0:
7892
return None

pydbus/registration.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .exitable import ExitableWithAliases
66
from functools import partial
77
from .method_call_context import MethodCallContext
8+
from . import unixfd
89
import logging
910

1011
try:
@@ -18,10 +19,12 @@ class ObjectWrapper(ExitableWithAliases("unwrap")):
1819
def __init__(self, object, interfaces):
1920
self.object = object
2021

22+
self.inargs = {}
2123
self.outargs = {}
2224
for iface in interfaces:
2325
for method in iface.methods:
2426
self.outargs[iface.name + "." + method.name] = [arg.signature for arg in method.out_args]
27+
self.inargs[iface.name + "." + method.name] = [arg.signature for arg in method.in_args]
2528

2629
self.readable_properties = {}
2730
self.writable_properties = {}
@@ -54,19 +57,23 @@ def onPropertiesChanged(iface, changed, invalidated):
5457
def call_method(self, connection, sender, object_path, interface_name, method_name, parameters, invocation):
5558
try:
5659
try:
60+
inargs = self.inargs[interface_name + "." + method_name]
5761
outargs = self.outargs[interface_name + "." + method_name]
5862
method = getattr(self.object, method_name)
5963
except KeyError:
6064
if interface_name == "org.freedesktop.DBus.Properties":
6165
if method_name == "Get":
6266
method = self.Get
6367
outargs = ["v"]
68+
inargs = ["ss"]
6469
elif method_name == "GetAll":
6570
method = self.GetAll
6671
outargs = ["a{sv}"]
72+
inargs = ["s"]
6773
elif method_name == "Set":
6874
method = self.Set
6975
outargs = []
76+
inargs = ["ssv"]
7077
else:
7178
raise
7279
else:
@@ -78,14 +85,23 @@ def call_method(self, connection, sender, object_path, interface_name, method_na
7885
if "dbus_context" in sig.parameters and sig.parameters["dbus_context"].kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY):
7986
kwargs["dbus_context"] = MethodCallContext(invocation)
8087

88+
if unixfd.is_supported(connection):
89+
parameters = unixfd.extract(
90+
parameters,
91+
inargs,
92+
invocation.get_message().get_unix_fd_list())
93+
8194
result = method(*parameters, **kwargs)
8295

8396
if len(outargs) == 0:
8497
invocation.return_value(None)
85-
elif len(outargs) == 1:
86-
invocation.return_value(GLib.Variant("(" + "".join(outargs) + ")", (result,)))
8798
else:
88-
invocation.return_value(GLib.Variant("(" + "".join(outargs) + ")", result))
99+
if len(outargs) == 1:
100+
result = (result, )
101+
if unixfd.is_supported(connection):
102+
invocation.return_value_with_unix_fd_list(GLib.Variant("(" + "".join(outargs) + ")", result), unixfd.make_fd_list(result, outargs, steal=True))
103+
else:
104+
invocation.return_value(GLib.Variant("(" + "".join(outargs) + ")", result))
89105

90106
except Exception as e:
91107
logger = logging.getLogger(__name__)
@@ -151,6 +167,5 @@ def register_object(self, path, object, node_info):
151167

152168
node_info = [Gio.DBusNodeInfo.new_for_xml(ni) for ni in node_info]
153169
interfaces = sum((ni.interfaces for ni in node_info), [])
154-
155170
wrapper = ObjectWrapper(object, interfaces)
156171
return ObjectRegistration(self, path, interfaces, wrapper, own_wrapper=True)

pydbus/unixfd.py

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from gi.repository import Gio
2+
3+
# signature type code
4+
TYPE_FD = "h"
5+
6+
def is_supported(conn):
7+
"""
8+
Check if the message bus supports passing of Unix file descriptors.
9+
"""
10+
return conn.get_capabilities() & Gio.DBusCapabilityFlags.UNIX_FD_PASSING
11+
12+
13+
def extract(params, signature, fd_list):
14+
"""
15+
Extract any file descriptors from a UnixFDList (e.g. after
16+
receiving from D-Bus) to a parameter list.
17+
Receiver must call os.dup on any fd it decides to keep/use.
18+
"""
19+
if not fd_list:
20+
return params
21+
return [fd_list.get(0)
22+
if arg == TYPE_FD
23+
else val
24+
for val, arg
25+
in zip(params, signature)]
26+
27+
28+
def make_fd_list(params, signature, steal=False):
29+
"""
30+
Embed any unix file descriptors in a parameter list into a
31+
UnixFDList (for D-Bus-dispatch).
32+
If steal is true, the responsibility for closing the file
33+
descriptors are transferred to the UnixFDList object.
34+
If steal is false, the file descriptors will be duplicated
35+
and the caller must close the original file descriptors.
36+
"""
37+
if not any(arg
38+
for arg in signature
39+
if arg == TYPE_FD):
40+
return None
41+
42+
fds = [param
43+
for param, arg
44+
in zip(params, signature)
45+
if arg == TYPE_FD]
46+
47+
if steal:
48+
return Gio.UnixFDList.new_from_array(fds)
49+
50+
fd_list = Gio.UnixFDList()
51+
for fd in fds:
52+
fd_list.append(fd)
53+
return fd_list

tests/run.sh

+1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ then
1515
"$PYTHON" $TESTS_DIR/publish.py
1616
"$PYTHON" $TESTS_DIR/publish_properties.py
1717
"$PYTHON" $TESTS_DIR/publish_multiface.py
18+
"$PYTHON" $TESTS_DIR/unixfd.py
1819
fi

tests/unixfd.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
from pydbus import SessionBus
2+
from gi.repository import GLib
3+
from threading import Thread
4+
import sys
5+
import os
6+
7+
loop = GLib.MainLoop()
8+
9+
10+
with open(__file__) as f:
11+
contents = f.read()
12+
13+
14+
class TestObject(object):
15+
"""
16+
<node>
17+
<interface name="baz.bar.Foo">
18+
<method name="Hello">
19+
<arg type="h" name="in_fd" direction="in"/>
20+
<arg type="h" name="out_fd" direction="out"/>
21+
</method>
22+
</interface>
23+
</node>
24+
"""
25+
def Hello(self, in_fd):
26+
with open(in_fd) as in_file:
27+
in_file.seek(0)
28+
assert(contents == in_file.read())
29+
print("Received fd as in parameter ok")
30+
with open(__file__) as out_file:
31+
assert(contents == out_file.read())
32+
return os.dup(out_file.fileno())
33+
34+
bus = SessionBus()
35+
36+
37+
with bus.publish("baz.bar.Foo", TestObject()):
38+
remote = bus.get("baz.bar.Foo")
39+
40+
def thread_func():
41+
with open(__file__) as in_file:
42+
assert(contents == in_file.read())
43+
out_fd = remote.Hello(in_file.fileno())
44+
with open(out_fd) as out_file:
45+
out_file.seek(0)
46+
assert(contents == out_file.read())
47+
print("Received fd as out argument ok")
48+
loop.quit()
49+
50+
thread = Thread(target=thread_func)
51+
thread.daemon = True
52+
53+
def handle_timeout():
54+
exit("ERROR: Timeout.")
55+
56+
GLib.timeout_add_seconds(2, handle_timeout)
57+
58+
thread.start()
59+
loop.run()
60+
thread.join()

0 commit comments

Comments
 (0)