blob: aec53d0340a46abfcee6b18e8363f04a65189e93 [file]
# Lint as: python2, python3
# Copyright 2013 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# Lint as: python3
""" Udevadm helper classes and functions.
"""
import re
import subprocess
import threading
class UdevadmInfo():
""" Use udevadm info on a specific path.
"""
@classmethod
def GetProperties(cls, syspath):
""" Get all properties of given syspath as a dict.
Args:
syspath: System path to get properties for.
Returns:
Dict with attribute/property as key and it's value. All keys are
converted to lowercase. Example: {'subsystem': 'input'}
"""
props = {}
rawprops = subprocess.check_output(' '.join(
['udevadm', 'info', '-q', 'property', '-p', syspath]),
shell=True)
for line in rawprops.splitlines():
upper_key, value = line.split(b'=', 1)
props[upper_key.lower()] = value.strip(b'"')
return props
class UdevadmTrigger():
""" Use udevadm trigger with specific rules.
"""
def __init__(self,
verbose=True,
event_type=None,
attr_match=[],
attr_nomatch=[],
subsystem_match=[],
subsystem_nomatch=[]):
""" Constructor
Args:
verbose: Whether to output triggered syspaths
event_type: What type of events to trigger (device or subsystem)
attr_match: What attributes to match
attr_nomatch: What attributes not to match
subsystem_match: What subsystems to match
subsystem_nomatch: What subsystems not to match
"""
cmd = ['udevadm', 'trigger']
if verbose:
cmd.append('-v')
if event_type:
cmd.append('-t')
cmd.append('"{}"'.format(event_type))
for attr in attr_match:
cmd.append('-a')
cmd.append('"{}"'.format(attr))
for attr in attr_nomatch:
cmd.append('-A')
cmd.append('"{}"'.format(attr))
for subsystem in subsystem_match:
cmd.append('-s')
cmd.append('"{}"'.format(subsystem))
for subsystem in subsystem_nomatch:
cmd.append('-S')
cmd.append('"{}"'.format(subsystem))
self.cmd = cmd
def DryRun(self):
""" Do a dry run using initialized trigger rules.
Returns:
List of syspaths that would be triggered.
"""
cmd = self.cmd + ['-n']
lines = subprocess.check_output(' '.join(cmd), shell=True)
return lines.splitlines() if lines else []
class UdevadmMonitor():
"""Use udevadm monitor to observe events.
The output of udevadm monitor looks like this:
KERNEL[71007.533873] change /devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:07/PNP0C09:00/PNP0C0A:00/power_supply/BAT0 (power_supply)
UDEV [71338.774623] remove /devices/pci0000:00/0000:00:14.0/usb3/3-10/wakeup/wakeup55 (wakeup)
UDEV [71338.789266] add /devices/virtual/misc/uhid/0005:1D6B:0246.003D/input/input70/event11 (input)
"""
# Regex to extract 5 groups: source, timestamp, event, path, subsystem.
MONITOR_REGEX = '(KERNEL|UDEV)[\s]*\[([0-9\.]+)\][\s]*([\S]*)[\s]*([\S]*)[\s]*\(([\S]*)\)'
def __init__(self, udev=False, kernel=False, subsystems=[], tags=[]):
"""Constructor.
@param udev: should we track udev events?
@param kernel: should we track kernel events?
@param subsystems: list of subsystems to monitor.
@param tags: list of tags to monitor.
"""
self.cmd = ['udevadm', 'monitor']
self.thread = None
self.proc = None
self.pattern = re.compile(self.MONITOR_REGEX)
if udev:
self.cmd.append('--udev')
if kernel:
self.cmd.append('--kernel')
for subsystem in subsystems:
self.cmd.append('--subsystem-match')
self.cmd.append('{}'.format(subsystem))
for tag in tags:
self.cmd.append('--tag-match')
self.cmd.append('{}'.format(tag))
def start(self, callback):
"""Starts the monitoring process.
@param callback: function to be called when monitor reports an event.
it should accept 5 parameters all in string:
(1) source (possible values: 'UDEV' or 'KERNEL')
(2) timestamp (e.g. '123.456789')
(3) event (e.g. 'add', 'change', 'remove')
(4) path (e.g. '/devices/virtual/misc/uhid/0005:1D6B:
0246.003D/input/input70/event11')
(5) subsystem (e.g. 'input', 'wakeup')
"""
def _monitoring_thread(proc, pattern, callback):
for line in proc.stdout:
match = pattern.match(line.decode('utf-8'))
if not match or len(match.groups()) != 5:
continue
callback(match.group(1), match.group(2), match.group(3),
match.group(4), match.group(5))
self.proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE)
self.thread = threading.Thread(target=_monitoring_thread,
args=(
self.proc,
self.pattern,
callback,
))
self.thread.start()
def stop(self):
"""Stops the monitoring process."""
if self.proc is not None:
self.proc.terminate()
self.thread.join()
self.proc = None
self.thread = None