35#error linux_devices requires unistd.h to be available
39#include <sys/sysmacros.h>
43#error linux_devices requires libusb-1.0 to be available
49#include "model/ocpn_utils.h"
52 std::string vendor_id;
53 std::string product_id;
56 std::string serial_nr;
58 usbdata(std::string v, std::string p,
const char* s = 0)
59 : vendor_id(v), product_id(p), serial_nr(s ? s :
"") {}
60 bool is_ok() {
return vendor_id.length() > 0; }
63static const int DONGLE_VENDOR = 0x1547;
64static const int DONGLE_PRODUCT = 0x1000;
66static const char*
const DONGLE_RULE = R
"--(
67ATTRS{idVendor}=="@vendor@", ATTRS{idProduct}=="@product@", MODE="0666"
70static const char*
const DEVICE_RULE = R
"--(
71ATTRS{idVendor}=="@vendor@", ATTRS{idProduct}=="@product@", \
72 MODE="0666", SYMLINK+="@symlink@"
75static const char*
const DEVICE_RULE_TTYS = R
"--(
76KERNEL=="ttyS@s_index@", MODE="0666"
79static const char*
const DONGLE_RULE_NAME =
"65-ocpn-dongle.rules";
82static void ReadUsbdata(libusb_device* dev, libusb_device_handle* handle,
84 struct libusb_device_descriptor desc;
85 libusb_get_device_descriptor(dev, &desc);
87 unsigned char buff[256];
90 r = libusb_get_string_descriptor_ascii(handle, desc.iProduct, buff,
92 if (r > 0) data->product =
reinterpret_cast<char*
>(buff);
94 if (desc.iManufacturer) {
95 r = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, buff,
97 if (r > 0) data->vendor =
reinterpret_cast<char*
>(buff);
99 if (desc.iSerialNumber) {
100 r = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, buff,
102 if (r > 0) data->serial_nr =
reinterpret_cast<char*
>(buff);
106static int TryOpen(
int vendorId,
int productId,
usbdata* data = 0) {
107 libusb_context* ctx = 0;
108 int r = libusb_init(&ctx);
110 auto e =
static_cast<libusb_error
>(r);
111 WARNING_LOG <<
"Cannot initialize libusb: " << libusb_strerror(e);
112 return LIBUSB_ERROR_NOT_SUPPORTED;
114 libusb_device** device_list;
115 ssize_t size = libusb_get_device_list(ctx, &device_list);
117 auto e =
static_cast<libusb_error
>(size);
118 DEBUG_LOG <<
"Cannot get usb devices list: " << libusb_strerror(e);
119 return LIBUSB_ERROR_NOT_SUPPORTED;
121 r = LIBUSB_ERROR_INVALID_PARAM;
122 for (
auto dev = device_list; *dev; dev++) {
123 struct libusb_device_descriptor desc;
124 libusb_get_device_descriptor(*dev, &desc);
125 if (desc.idVendor != vendorId || desc.idProduct != productId) {
128 libusb_device_handle* dev_handle;
129 r = libusb_open(*dev, &dev_handle);
132 ReadUsbdata(*dev, dev_handle, data);
134 libusb_close(dev_handle);
138 libusb_free_device_list(device_list, 1);
139 DEBUG_LOG <<
"Nothing found for " << vendorId <<
":" << productId;
144static int TryOpen(
const std::string vendorId,
const std::string productId,
148 std::istringstream(vendorId) >> std::hex >> v;
149 std::istringstream(productId) >> std::hex >> p;
150 return TryOpen(v, p, data);
154 int rc = TryOpen(DONGLE_VENDOR, DONGLE_PRODUCT);
155 DEBUG_LOG <<
"Probing dongle permissions, result: " << rc;
156 return rc == LIBUSB_ERROR_ACCESS;
160 int r = access(path, R_OK | W_OK);
162 INFO_LOG <<
"access(3) fails on: " << path <<
": " << strerror(errno);
168static usbdata ParseUevent(std::istream& is) {
170 while (std::getline(is, line)) {
171 if (line.find(
'=') == std::string::npos) {
174 auto tokens = ocpn::split(line.c_str(),
"=");
175 if (tokens[0] !=
"PRODUCT") {
178 if (line.find(
"/") == std::string::npos) {
179 INFO_LOG <<
"invalid product line: " << line <<
"(ignored)";
182 tokens = ocpn::split(tokens[1].c_str(),
"/");
183 std::stringstream ss1;
184 ss1 << std::setfill(
'0') << std::setw(4) << tokens[0];
185 std::stringstream ss2;
186 ss2 << std::setfill(
'0') << std::setw(4) << tokens[1];
187 return usbdata(ss1.str(), ss2.str());
192static usbdata GetDeviceUsbdata(
const char* path) {
195 int r = stat(path, &st);
197 MESSAGE_LOG <<
"Cannot stat: " << path <<
": " << strerror(errno);
200 std::stringstream syspath(
"/sys/dev/char/");
201 syspath <<
"/sys/dev/char/" << major(st.st_rdev) <<
":" << minor(st.st_rdev);
203 if (!realpath(syspath.str().c_str(), buff)) {
204 wxLogDebug(
"Error resolving link %s: %s", syspath.str().c_str(),
207 std::string real_path(buff);
210 while (real_path.length() > 0) {
211 auto uevent_path = real_path +
"/uevent";
212 if (access(uevent_path.c_str(), R_OK) >= 0) {
213 std::ifstream is(uevent_path);
214 auto data = ParseUevent(is);
217 TryOpen(data.vendor_id, data.product_id, &data);
222 size_t last_slash = real_path.rfind(
'/');
223 last_slash = last_slash == std::string::npos ? 0 : last_slash;
224 real_path = real_path.substr(0, last_slash);
229static std::string TmpRulePath(
const char* name) {
231 getenv(
"XDG_CACHE_HOME") ? getenv(
"XDG_CACHE_HOME") :
"/tmp";
232 tmpdir +=
"/udevXXXXXX";
234 char dirpath[128] = {0};
235 strncpy(dirpath, tmpdir.c_str(),
sizeof(dirpath) - 1);
236 if (!mkdtemp(dirpath)) {
237 WARNING_LOG <<
"Cannot create tempdir: " << strerror(errno);
238 MESSAGE_LOG <<
"Using /tmp";
239 strcpy(dirpath,
"/tmp");
241 std::string path(dirpath);
248 for (
char ch : {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9'}) {
249 std::stringstream ss;
250 ss <<
"/etc/udev/rules.d/65-opencpn" << ch <<
".rules";
251 if (!ocpn::exists(ss.str())) {
252 std::string path(ss.str());
253 ocpn::replace(path,
"/etc/udev/rules.d/65-",
"");
254 ocpn::replace(path,
".rules",
"");
258 WARNING_LOG <<
"Too many opencpn device rules found (10). Giving up.";
262static std::string CreateTmpfile(
const std::string& contents,
264 auto path = TmpRulePath(name);
265 std::ofstream of(path);
269 WARNING_LOG <<
"Cannot write to temp file: " << path;
274static std::string CreateUdevRule(
const std::string& device,
usbdata data,
276 std::string rule(DEVICE_RULE);
277 if (device.find(
"ttyS") != std::string::npos) {
278 rule = std::string(DEVICE_RULE_TTYS);
279 auto index(device.substr(device.find(
"ttyS") + strlen(
"ttyS")));
280 ocpn::replace(rule,
"@s_index@", index);
282 ocpn::replace(rule,
"@vendor@", data.vendor_id);
283 ocpn::replace(rule,
"@product@", data.product_id);
285 ocpn::replace(rule,
"@symlink@",
symlink);
287 name.insert(0,
"65-");
289 return CreateTmpfile(rule, name.c_str());
293 std::string rule(DONGLE_RULE);
294 std::ostringstream oss;
296 oss << std::setw(4) << std::setfill(
'0') << std::hex << DONGLE_VENDOR;
297 ocpn::replace(rule,
"@vendor@", oss.str());
299 oss << std::setw(4) << std::setfill(
'0') << std::hex << DONGLE_PRODUCT;
300 ocpn::replace(rule,
"@product@", oss.str());
301 return CreateTmpfile(rule, DONGLE_RULE_NAME);
305 usbdata data = GetDeviceUsbdata(device);
306 auto path = CreateUdevRule(device, data,
symlink);
std::string MakeUdevLink()
Get next available udev rule base name.
bool IsDevicePermissionsOk(const char *path)
Check device path permissions.
std::string GetDongleRule()
std::string GetDeviceRule(const char *device, const char *symlink)
Get device udev rule.
bool IsDonglePermissionsWrong()
Return true if an existing dongle cannot be accessed.
Low level udev usb device management.
Enhanced logging interface on top of wx/log.h.