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
47#include "linux_devices.h"
49#include "ocpn_utils.h"
52 std::string vendor_id;
53 std::string product_id;
56 std::string serial_nr;
58 usbdata(
const std::string& v,
const 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 DONGLE_RULE_NAME =
"65-ocpn-dongle.rules";
77static void read_usbdata(libusb_device* dev, libusb_device_handle* handle,
79 struct libusb_device_descriptor desc;
80 libusb_get_device_descriptor(dev, &desc);
83 ss << std::setfill(
'0') << std::setw(4) << desc.idVendor;
84 data->vendor_id = std::string(ss.str());
86 ss << std::setfill(
'0') << std::setw(4) << desc.idProduct;
87 data->vendor_id = std::string(ss.str());
89 unsigned char buff[256];
92 r = libusb_get_string_descriptor_ascii(handle, desc.iProduct, buff,
94 if (r > 0) data->product =
reinterpret_cast<char*
>(buff);
96 if (desc.iManufacturer) {
97 r = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, buff,
99 if (r > 0) data->vendor =
reinterpret_cast<char*
>(buff);
101 if (desc.iSerialNumber) {
102 r = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, buff,
104 if (r > 0) data->serial_nr =
reinterpret_cast<char*
>(buff);
108static int try_open(
int vendorId,
int productId,
usbdata* data = 0) {
109 libusb_context* ctx = 0;
110 int r = libusb_init(&ctx);
112 auto e =
static_cast<libusb_error
>(r);
113 WARNING_LOG <<
"Cannot initialize libusb: " << libusb_strerror(e);
114 return LIBUSB_ERROR_NOT_SUPPORTED;
116 libusb_device** device_list;
117 ssize_t size = libusb_get_device_list(ctx, &device_list);
119 auto e =
static_cast<libusb_error
>(size);
120 DEBUG_LOG <<
"Cannot get usb devices list: " << libusb_strerror(e);
121 return LIBUSB_ERROR_NOT_SUPPORTED;
123 r = LIBUSB_ERROR_INVALID_PARAM;
124 for (
auto dev = device_list; *dev; dev++) {
125 struct libusb_device_descriptor desc;
126 libusb_get_device_descriptor(*dev, &desc);
127 if (desc.idVendor != vendorId || desc.idProduct != productId) {
130 libusb_device_handle* dev_handle;
131 r = libusb_open(*dev, &dev_handle);
134 read_usbdata(*dev, dev_handle, data);
136 libusb_close(dev_handle);
140 libusb_free_device_list(device_list, 1);
141 DEBUG_LOG <<
"Nothing found for " << vendorId <<
":" << productId;
146static int try_open(
const std::string vendorId,
const std::string productId,
150 std::istringstream(vendorId) >> std::hex >> v;
151 std::istringstream(productId) >> std::hex >> p;
152 return try_open(v, p, data);
155bool is_dongle_permissions_wrong() {
156 int rc = try_open(DONGLE_VENDOR, DONGLE_PRODUCT);
157 DEBUG_LOG <<
"Probing dongle permissions, result: " << rc;
158 return rc == LIBUSB_ERROR_ACCESS;
161bool is_device_permissions_ok(
const char* path) {
162 int r = access(path, R_OK | W_OK);
164 INFO_LOG <<
"access(3) fails on: " << path <<
": " << strerror(errno);
170static usbdata parse_uevent(std::istream& is) {
172 while (std::getline(is, line)) {
173 if (line.find(
'=') == std::string::npos) {
176 auto tokens = ocpn::split(line.c_str(),
"=");
177 if (tokens[0] !=
"PRODUCT") {
180 if (line.find(
"/") == std::string::npos) {
181 INFO_LOG <<
"invalid product line: " << line <<
"(ignored)";
184 tokens = ocpn::split(tokens[1].c_str(),
"/");
185 std::stringstream ss1;
186 ss1 << std::setfill(
'0') << std::setw(4) << tokens[0];
187 std::stringstream ss2;
188 ss2 << std::setfill(
'0') << std::setw(4) << tokens[1];
189 return usbdata(ss1.str(), ss2.str());
194static usbdata get_device_usbdata(
const char* path) {
197 int r = stat(path, &st);
199 MESSAGE_LOG <<
"Cannot stat: " << path <<
": " << strerror(errno);
202 std::stringstream syspath(
"/sys/dev/char/");
203 syspath <<
"/sys/dev/char/" << major(st.st_rdev) <<
":" << minor(st.st_rdev);
205 realpath(syspath.str().c_str(), buff);
206 std::string real_path(buff);
209 while (real_path.length() > 0) {
210 auto uevent_path = real_path +
"/uevent";
211 if (access(uevent_path.c_str(), R_OK) >= 0) {
212 std::ifstream is(uevent_path);
213 auto data = parse_uevent(is);
216 try_open(data.vendor_id, data.product_id, &data);
221 size_t last_slash = real_path.rfind(
'/');
222 last_slash = last_slash == std::string::npos ? 0 : last_slash;
223 real_path = real_path.substr(0, last_slash);
228static std::string tmp_rule_path(
const char* name) {
230 getenv(
"XDG_CACHE_HOME") ? getenv(
"XDG_CACHE_HOME") :
"/tmp";
231 tmpdir +=
"/udevXXXXXX";
233 char dirpath[128] = {0};
234 strncpy(dirpath, tmpdir.c_str(),
sizeof(dirpath) - 1);
235 if (!mkdtemp(dirpath)) {
236 WARNING_LOG <<
"Cannot create tempdir: " << strerror(errno);
237 MESSAGE_LOG <<
"Using /tmp";
238 strcpy(dirpath,
"/tmp");
240 std::string path(dirpath);
246std::string make_udev_link() {
247 for (
char ch : {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9'}) {
248 std::string path(
"/dev/opencpn");
250 if (!ocpn::exists(path)) {
251 ocpn::replace(path,
"/dev/",
"");
255 WARNING_LOG <<
"Too many opencpn devices found (10). Giving up.";
259static std::string create_tmpfile(
const std::string& contents,
261 auto path = tmp_rule_path(name);
262 std::ofstream of(path);
266 WARNING_LOG <<
"Cannot write to temp file: " << path;
271static std::string create_udev_rule(
usbdata data,
const char*
symlink) {
272 std::string rule(DEVICE_RULE);
273 ocpn::replace(rule,
"@vendor@", data.vendor_id);
274 ocpn::replace(rule,
"@product@", data.product_id);
275 ocpn::replace(rule,
"@symlink@",
symlink);
278 name.insert(0,
"65-");
280 return create_tmpfile(rule, name.c_str());
283std::string get_dongle_rule() {
284 std::string rule(DONGLE_RULE);
285 std::ostringstream oss;
287 oss << std::setw(4) << std::setfill(
'0') << std::hex << DONGLE_VENDOR;
288 ocpn::replace(rule,
"@vendor@", oss.str());
290 oss << std::setw(4) << std::setfill(
'0') << std::hex << DONGLE_PRODUCT;
291 ocpn::replace(rule,
"@product@", oss.str());
292 return create_tmpfile(rule, DONGLE_RULE_NAME);
295std::string get_device_rule(
const char* device,
const char*
symlink) {
296 usbdata data = get_device_usbdata(device);
297 auto path = create_udev_rule(data,
symlink);