395
395
logger.error(bad_states[state] + ": %r", error)
397
397
elif state == avahi.SERVER_RUNNING:
400
except dbus.exceptions.DBusException as error:
401
if (error.get_dbus_name()
402
== "org.freedesktop.Avahi.CollisionError"):
403
logger.info("Local Zeroconf service name"
405
return self.rename(remove=False)
407
logger.critical("D-Bus Exception", exc_info=error)
411
400
if error is None:
412
401
logger.debug("Unknown state: %r", state)
476
456
last_checker_status: integer between 0 and 255 reflecting exit
477
457
status of last checker. -1 reflects crashed
478
458
checker, -2 means no checker completed yet.
479
last_checker_signal: The signal which killed the last checker, if
480
last_checker_status is -1
481
459
last_enabled: datetime.datetime(); (UTC) or None
482
460
name: string; from the config file, used in log messages and
483
461
D-Bus identifiers
657
635
# Also start a new checker *right now*.
658
636
self.start_checker()
660
def checker_callback(self, source, condition, connection,
638
def checker_callback(self, pid, condition, command):
662
639
"""The checker has completed, so take appropriate actions."""
663
640
self.checker_callback_tag = None
664
641
self.checker = None
665
# Read return code from connection (see call_pipe)
666
returncode = connection.recv()
670
self.last_checker_status = returncode
671
self.last_checker_signal = None
642
if os.WIFEXITED(condition):
643
self.last_checker_status = os.WEXITSTATUS(condition)
672
644
if self.last_checker_status == 0:
673
645
logger.info("Checker for %(name)s succeeded",
677
649
logger.info("Checker for %(name)s failed", vars(self))
679
651
self.last_checker_status = -1
680
self.last_checker_signal = -returncode
681
652
logger.warning("Checker for %(name)s crashed?",
685
655
def checked_ok(self):
686
656
"""Assert that the client has been seen, alive and well."""
687
657
self.last_checked_ok = datetime.datetime.utcnow()
688
658
self.last_checker_status = 0
689
self.last_checker_signal = None
690
659
self.bump_timeout()
692
661
def bump_timeout(self, timeout=None):
718
687
# than 'timeout' for the client to be disabled, which is as it
721
if self.checker is not None and not self.checker.is_alive():
722
logger.warning("Checker was not alive; joining")
690
# If a checker exists, make sure it is not a zombie
692
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
693
except AttributeError:
695
except OSError as error:
696
if error.errno != errno.ECHILD:
700
logger.warning("Checker was a zombie")
701
gobject.source_remove(self.checker_callback_tag)
702
self.checker_callback(pid, status,
703
self.current_checker_command)
725
704
# Start a new checker if needed
726
705
if self.checker is None:
727
706
# Escape attributes for the shell
737
716
return True # Try again later
738
717
self.current_checker_command = command
739
logger.info("Starting checker %r for %s", command,
741
# We don't need to redirect stdout and stderr, since
742
# in normal mode, that is already done by daemon(),
743
# and in debug mode we don't want to. (Stdin is
744
# always replaced by /dev/null.)
745
# The exception is when not debugging but nevertheless
746
# running in the foreground; use the previously
748
popen_args = { "close_fds": True,
751
if (not self.server_settings["debug"]
752
and self.server_settings["foreground"]):
753
popen_args.update({"stdout": wnull,
755
pipe = multiprocessing.Pipe(duplex = False)
756
self.checker = multiprocessing.Process(
758
args = (pipe[1], subprocess.call, command),
761
self.checker_callback_tag = gobject.io_add_watch(
762
pipe[0].fileno(), gobject.IO_IN,
763
self.checker_callback, pipe[0], command)
719
logger.info("Starting checker %r for %s", command,
721
# We don't need to redirect stdout and stderr, since
722
# in normal mode, that is already done by daemon(),
723
# and in debug mode we don't want to. (Stdin is
724
# always replaced by /dev/null.)
725
# The exception is when not debugging but nevertheless
726
# running in the foreground; use the previously
729
if (not self.server_settings["debug"]
730
and self.server_settings["foreground"]):
731
popen_args.update({"stdout": wnull,
733
self.checker = subprocess.Popen(command,
738
except OSError as error:
739
logger.error("Failed to start subprocess",
742
self.checker_callback_tag = gobject.child_watch_add(
743
self.checker.pid, self.checker_callback, data=command)
744
# The checker may have completed before the gobject
745
# watch was added. Check for this.
747
pid, status = os.waitpid(self.checker.pid, os.WNOHANG)
748
except OSError as error:
749
if error.errno == errno.ECHILD:
750
# This should never happen
751
logger.error("Child process vanished",
756
gobject.source_remove(self.checker_callback_tag)
757
self.checker_callback(pid, status, command)
764
758
# Re-run this periodically if run by gobject.timeout_add
874
class DBusObjectWithAnnotations(dbus.service.Object):
875
"""A D-Bus object with annotations.
873
class DBusObjectWithProperties(dbus.service.Object):
874
"""A D-Bus object with properties.
877
Classes inheriting from this can use the dbus_annotations
878
decorator to add annotations to methods or signals.
876
Classes inheriting from this can use the dbus_service_property
877
decorator to expose methods as D-Bus properties. It exposes the
878
standard Get(), Set(), and GetAll() methods on the D-Bus.
897
897
for name, athing in
898
898
inspect.getmembers(cls, self._is_dbus_thing(thing)))
900
@dbus.service.method(dbus.INTROSPECTABLE_IFACE,
902
path_keyword = 'object_path',
903
connection_keyword = 'connection')
904
def Introspect(self, object_path, connection):
905
"""Overloading of standard D-Bus method.
907
Inserts annotation tags on methods and signals.
909
xmlstring = dbus.service.Object.Introspect(self, object_path,
912
document = xml.dom.minidom.parseString(xmlstring)
914
for if_tag in document.getElementsByTagName("interface"):
915
# Add annotation tags
916
for typ in ("method", "signal"):
917
for tag in if_tag.getElementsByTagName(typ):
919
for name, prop in (self.
920
_get_all_dbus_things(typ)):
921
if (name == tag.getAttribute("name")
922
and prop._dbus_interface
923
== if_tag.getAttribute("name")):
924
annots.update(getattr(
925
prop, "_dbus_annotations", {}))
926
for name, value in annots.items():
927
ann_tag = document.createElement(
929
ann_tag.setAttribute("name", name)
930
ann_tag.setAttribute("value", value)
931
tag.appendChild(ann_tag)
932
# Add interface annotation tags
933
for annotation, value in dict(
934
itertools.chain.from_iterable(
935
annotations().items()
936
for name, annotations
937
in self._get_all_dbus_things("interface")
938
if name == if_tag.getAttribute("name")
940
ann_tag = document.createElement("annotation")
941
ann_tag.setAttribute("name", annotation)
942
ann_tag.setAttribute("value", value)
943
if_tag.appendChild(ann_tag)
944
# Fix argument name for the Introspect method itself
945
if (if_tag.getAttribute("name")
946
== dbus.INTROSPECTABLE_IFACE):
947
for cn in if_tag.getElementsByTagName("method"):
948
if cn.getAttribute("name") == "Introspect":
949
for arg in cn.getElementsByTagName("arg"):
950
if (arg.getAttribute("direction")
952
arg.setAttribute("name",
954
xmlstring = document.toxml("utf-8")
956
except (AttributeError, xml.dom.DOMException,
957
xml.parsers.expat.ExpatError) as error:
958
logger.error("Failed to override Introspection method",
963
class DBusObjectWithProperties(DBusObjectWithAnnotations):
964
"""A D-Bus object with properties.
966
Classes inheriting from this can use the dbus_service_property
967
decorator to expose methods as D-Bus properties. It exposes the
968
standard Get(), Set(), and GetAll() methods on the D-Bus.
971
900
def _get_dbus_property(self, interface_name, property_name):
972
901
"""Returns a bound method if one exists which is a D-Bus
973
902
property with the specified name and interface.
1079
1007
if prop._dbus_interface
1080
1008
== if_tag.getAttribute("name")):
1081
1009
if_tag.appendChild(tag)
1082
# Add annotation tags for properties
1083
for tag in if_tag.getElementsByTagName("property"):
1085
for name, prop in self._get_all_dbus_things(
1087
if (name == tag.getAttribute("name")
1088
and prop._dbus_interface
1089
== if_tag.getAttribute("name")):
1090
annots.update(getattr(
1091
prop, "_dbus_annotations", {}))
1092
for name, value in annots.items():
1093
ann_tag = document.createElement(
1095
ann_tag.setAttribute("name", name)
1096
ann_tag.setAttribute("value", value)
1097
tag.appendChild(ann_tag)
1010
# Add annotation tags
1011
for typ in ("method", "signal", "property"):
1012
for tag in if_tag.getElementsByTagName(typ):
1014
for name, prop in (self.
1015
_get_all_dbus_things(typ)):
1016
if (name == tag.getAttribute("name")
1017
and prop._dbus_interface
1018
== if_tag.getAttribute("name")):
1019
annots.update(getattr(
1020
prop, "_dbus_annotations", {}))
1021
for name, value in annots.items():
1022
ann_tag = document.createElement(
1024
ann_tag.setAttribute("name", name)
1025
ann_tag.setAttribute("value", value)
1026
tag.appendChild(ann_tag)
1027
# Add interface annotation tags
1028
for annotation, value in dict(
1029
itertools.chain.from_iterable(
1030
annotations().items()
1031
for name, annotations
1032
in self._get_all_dbus_things("interface")
1033
if name == if_tag.getAttribute("name")
1035
ann_tag = document.createElement("annotation")
1036
ann_tag.setAttribute("name", annotation)
1037
ann_tag.setAttribute("value", value)
1038
if_tag.appendChild(ann_tag)
1098
1039
# Add the names to the return values for the
1099
1040
# "org.freedesktop.DBus.Properties" methods
1100
1041
if (if_tag.getAttribute("name")
1170
1111
interface_names.add(alt_interface)
1171
1112
# Is this a D-Bus signal?
1172
1113
if getattr(attribute, "_dbus_is_signal", False):
1173
if sys.version_info.major == 2:
1174
# Extract the original non-method undecorated
1175
# function by black magic
1176
nonmethod_func = (dict(
1177
zip(attribute.func_code.co_freevars,
1178
attribute.__closure__))
1179
["func"].cell_contents)
1181
nonmethod_func = attribute
1114
# Extract the original non-method undecorated
1115
# function by black magic
1116
nonmethod_func = (dict(
1117
zip(attribute.func_code.co_freevars,
1118
attribute.__closure__))
1119
["func"].cell_contents)
1182
1120
# Create a new, but exactly alike, function
1183
1121
# object, and decorate it to be a new D-Bus signal
1184
1122
# with the alternate D-Bus interface name
1185
if sys.version_info.major == 2:
1186
new_function = types.FunctionType(
1187
nonmethod_func.func_code,
1188
nonmethod_func.func_globals,
1189
nonmethod_func.func_name,
1190
nonmethod_func.func_defaults,
1191
nonmethod_func.func_closure)
1193
new_function = types.FunctionType(
1194
nonmethod_func.__code__,
1195
nonmethod_func.__globals__,
1196
nonmethod_func.__name__,
1197
nonmethod_func.__defaults__,
1198
nonmethod_func.__closure__)
1199
1123
new_function = (dbus.service.signal(
1201
attribute._dbus_signature)(new_function))
1124
alt_interface, attribute._dbus_signature)
1125
(types.FunctionType(
1126
nonmethod_func.func_code,
1127
nonmethod_func.func_globals,
1128
nonmethod_func.func_name,
1129
nonmethod_func.func_defaults,
1130
nonmethod_func.func_closure)))
1202
1131
# Copy annotations, if any
1204
1133
new_function._dbus_annotations = dict(
1427
1356
DBusObjectWithProperties.__del__(self, *args, **kwargs)
1428
1357
Client.__del__(self, *args, **kwargs)
1430
def checker_callback(self, source, condition,
1431
connection, command, *args, **kwargs):
1432
ret = Client.checker_callback(self, source, condition,
1433
connection, command, *args,
1435
exitstatus = self.last_checker_status
1359
def checker_callback(self, pid, condition, command,
1361
self.checker_callback_tag = None
1363
if os.WIFEXITED(condition):
1364
exitstatus = os.WEXITSTATUS(condition)
1437
1365
# Emit D-Bus signal
1438
1366
self.CheckerCompleted(dbus.Int16(exitstatus),
1439
# This is specific to GNU libC
1440
dbus.Int64(exitstatus << 8),
1367
dbus.Int64(condition),
1441
1368
dbus.String(command))
1443
1370
# Emit D-Bus signal
1444
1371
self.CheckerCompleted(dbus.Int16(-1),
1446
# This is specific to GNU libC
1448
| self.last_checker_signal),
1372
dbus.Int64(condition),
1449
1373
dbus.String(command))
1375
return Client.checker_callback(self, pid, condition, command,
1452
1378
def start_checker(self, *args, **kwargs):
1453
1379
old_checker_pid = getattr(self.checker, "pid", None)
1591
1513
self.approval_duration = datetime.timedelta(0, 0, 0, value)
1593
1515
# Name - property
1595
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1596
1516
@dbus_service_property(_interface, signature="s", access="read")
1597
1517
def Name_dbus_property(self):
1598
1518
return dbus.String(self.name)
1600
1520
# Fingerprint - property
1602
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const"})
1603
1521
@dbus_service_property(_interface, signature="s", access="read")
1604
1522
def Fingerprint_dbus_property(self):
1605
1523
return dbus.String(self.fingerprint)
1736
1652
self.stop_checker()
1738
1654
# ObjectPath - property
1740
{"org.freedesktop.DBus.Property.EmitsChangedSignal": "const",
1741
"org.freedesktop.DBus.Deprecated": "true"})
1742
1655
@dbus_service_property(_interface, signature="o", access="read")
1743
1656
def ObjectPath_dbus_property(self):
1744
1657
return self.dbus_object_path # is already a dbus.ObjectPath
1746
1659
# Secret = property
1748
{"org.freedesktop.DBus.Property.EmitsChangedSignal":
1750
1660
@dbus_service_property(_interface,
1751
1661
signature="ay",
1752
1662
access="write",
2273
2182
# avoid excessive use of external libraries.
2275
2184
# New type for defining tokens, syntax, and semantics all-in-one
2185
Token = collections.namedtuple("Token",
2186
("regexp", # To match token; if
2187
# "value" is not None,
2188
# must have a "group"
2190
"value", # datetime.timedelta or
2192
"followers")) # Tokens valid after
2276
2194
Token = collections.namedtuple("Token", (
2277
2195
"regexp", # To match token; if "value" is not None, must have
2278
2196
# a "group" containing digits