From e706263fea9ca62d9cccd9bda40fd733108614ce Mon Sep 17 00:00:00 2001 From: Shin'ichiro Kawasaki Date: Sun, 20 Sep 2020 20:51:44 +0900 Subject: [PATCH] bluepy_helper_cap.py: Check bluepy-helper capability at scratch-link run To run bluepy-scratch-link as a normal user bluepy-helper must have cap_net_admin and cap_net_raw capabilities to connect to BLE devices using bluepy. When it lacks these capabilities, bluepy-scratch-link fails to discover BLE devices without message about the failure reason. To inform users the reason of BLE device detection failure, check capabilities of bluepy-helper when discovery of BLE device is requested. If the capabilities are not set, print error messages and request users to run script to set the capabilities. Implement this features in bluepy_helper_cap.py. In addition, re-implement bash script setcap.sh feature as python and added to bluepy_helper_cap.py to simplify the code set. Signed-off-by: Shin'ichiro Kawasaki --- README.md | 5 ++-- bluepy_helper_cap.py | 65 ++++++++++++++++++++++++++++++++++++++++++++ scratch_link.py | 6 ++++ setcap.sh | 6 ---- 4 files changed, 73 insertions(+), 9 deletions(-) create mode 100755 bluepy_helper_cap.py delete mode 100755 setcap.sh diff --git a/README.md b/README.md index 48c3f57..1e418fc 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,8 @@ Installation 5. Set bluepy-helper capability ``` - ./setcap.sh - Set up bluepy-helper capability to allow use by normal users - /usr/lib/python3.8/site-packages/bluepy-1.3.0-py3.8.egg/bluepy/bluepy-helper = cap_net_admin,cap_net_raw+eip + $ sudo ./bluepy_helper_cap.py + Set capacbility 'cap_net_raw,cap_net_admin' to /usr/lib/python3.8/site-packages/bluepy-1.3.0-py3.8.egg/bluepy/bluepy-helper ``` 6. If using a micro:bit, install Scratch-link hex on your device. diff --git a/bluepy_helper_cap.py b/bluepy_helper_cap.py new file mode 100755 index 0000000..00de779 --- /dev/null +++ b/bluepy_helper_cap.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +import sys +import os +import shutil +import bluepy +import subprocess + +import logging +logLevel = logging.INFO + +# for logging +logger = logging.getLogger(__name__) +formatter = logging.Formatter(fmt='%(asctime)s %(message)s') +handler = logging.StreamHandler() +handler.setLevel(logLevel) +handler.setFormatter(formatter) +logger.setLevel(logLevel) +logger.addHandler(handler) +logger.propagate = False + +# Check dependent tools +DEPENDENT_TOOLS = { + "setcap": "libcap2-bin (Ubuntu) or libcap (Arch)", +} + +for cmd in DEPENDENT_TOOLS: + if not shutil.which(cmd): + print(f"'{cmd}' not found. Install package {DEPENDENT_TOOLS[cmd]}.") + sys.exit(1) + +def helper_path(): + path = os.path.abspath(bluepy.__file__) + if not path: + logger.error("Bluepy module not found") + sys.exit(1) + if path.find("__init__.py") < 0: + logger.error(f"Unexpected bluepy module path: {path}") + sys.exit(1) + path = path.replace("__init__.py", "bluepy-helper") + return path + +def is_set(): + path = helper_path() + p = subprocess.run(["getcap", path], capture_output=True) + if p.returncode !=0: + logger.error(f"Failed to get capability of {path}") + return False + out = str(p.stdout) + return out.find("cap_net_admin") >= 0 and out.find("cap_net_raw") >= 0 + +def setcap(): + path = helper_path() + if is_set(): + return True + p = subprocess.run(["setcap", "cap_net_raw,cap_net_admin+eip", path], \ + capture_output=True) + if p.returncode !=0: + logger.error(f"Failed to set capability to {path}") + return False + print(f"Set capacbility 'cap_net_raw,cap_net_admin' to {path}") + return True + +if __name__ == "__main__": + setcap() diff --git a/scratch_link.py b/scratch_link.py index f577a69..9d9945a 100755 --- a/scratch_link.py +++ b/scratch_link.py @@ -21,6 +21,7 @@ import bluetooth # for BLESession (e.g. BBC micro:bit) from bluepy.btle import Scanner, UUID, Peripheral, DefaultDelegate from bluepy.btle import BTLEDisconnectError, BTLEManagementError +import bluepy_helper_cap import threading import time @@ -518,6 +519,11 @@ class BLESession(Session): err_msg = None if self.status == self.INITIAL and method == 'discover': + if not bluepy_helper_cap.is_set(): + logger.error("Capability is not set to bluepy helper.") + logger.error("Run bluepy_setcap.py with root privilege.") + logger.error("e.g. $ sudo bluepy_helper_cap.py") + sys.exit(1) found_ifaces = 0 for i in range(self.MAX_SCANNER_IF): scanner = Scanner(iface=i) diff --git a/setcap.sh b/setcap.sh deleted file mode 100755 index df58ccf..0000000 --- a/setcap.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -echo "Set up bluepy-helper capability to allow use by normal users" -find /usr -name bluepy-helper -exec sudo setcap \ - 'cap_net_raw,cap_net_admin+eip' {} \; 2> /dev/null -find /usr -name bluepy-helper -exec sudo getcap {} \; 2> /dev/null -