32 #error linux_devices requires unistd.h to be available
36 #include <sys/sysmacros.h>
39 #ifndef HAVE_LIBUSB_10
40 #error linux_devices requires libusb-1.0 to be available
45 #include "model/logger.h"
46 #include "model/ocpn_utils.h"
49 std::string vendor_id;
50 std::string product_id;
53 std::string serial_nr;
55 usbdata(std::string v, std::string p,
const char* s = 0)
56 : vendor_id(v), product_id(p), serial_nr(s ? s :
"") {}
57 bool is_ok() {
return vendor_id.length() > 0; }
60 static const int DONGLE_VENDOR = 0x1547;
61 static const int DONGLE_PRODUCT = 0x1000;
63 static const char*
const DONGLE_RULE = R
"--(
64 ATTRS{idVendor}=="@vendor@", ATTRS{idProduct}=="@product@", MODE="0666"
67 static const char*
const DEVICE_RULE = R
"--(
68 ATTRS{idVendor}=="@vendor@", ATTRS{idProduct}=="@product@", \
69 MODE="0666", SYMLINK+="@symlink@"
72 static const char*
const DEVICE_RULE_TTYS = R
"--(
73 KERNEL=="ttyS@s_index@", MODE="0666"
76 static const char*
const DONGLE_RULE_NAME =
"65-ocpn-dongle.rules";
79 static void ReadUsbdata(libusb_device* dev, libusb_device_handle* handle,
81 struct libusb_device_descriptor desc;
82 libusb_get_device_descriptor(dev, &desc);
84 unsigned char buff[256];
87 r = libusb_get_string_descriptor_ascii(handle, desc.iProduct, buff,
89 if (r > 0) data->product =
reinterpret_cast<char*
>(buff);
91 if (desc.iManufacturer) {
92 r = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, buff,
94 if (r > 0) data->vendor =
reinterpret_cast<char*
>(buff);
96 if (desc.iSerialNumber) {
97 r = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, buff,
99 if (r > 0) data->serial_nr =
reinterpret_cast<char*
>(buff);
103 static int TryOpen(
int vendorId,
int productId,
usbdata* data = 0) {
104 libusb_context* ctx = 0;
105 int r = libusb_init(&ctx);
107 auto e =
static_cast<libusb_error
>(r);
108 WARNING_LOG <<
"Cannot initialize libusb: " << libusb_strerror(e);
109 return LIBUSB_ERROR_NOT_SUPPORTED;
111 libusb_device** device_list;
112 ssize_t size = libusb_get_device_list(ctx, &device_list);
114 auto e =
static_cast<libusb_error
>(size);
115 DEBUG_LOG <<
"Cannot get usb devices list: " << libusb_strerror(e);
116 return LIBUSB_ERROR_NOT_SUPPORTED;
118 r = LIBUSB_ERROR_INVALID_PARAM;
119 for (
auto dev = device_list; *dev; dev++) {
120 struct libusb_device_descriptor desc;
121 libusb_get_device_descriptor(*dev, &desc);
122 if (desc.idVendor != vendorId || desc.idProduct != productId) {
125 libusb_device_handle* dev_handle;
126 r = libusb_open(*dev, &dev_handle);
129 ReadUsbdata(*dev, dev_handle, data);
131 libusb_close(dev_handle);
135 libusb_free_device_list(device_list, 1);
136 DEBUG_LOG <<
"Nothing found for " << vendorId <<
":" << productId;
141 static int TryOpen(
const std::string vendorId,
const std::string productId,
145 std::istringstream(vendorId) >> std::hex >> v;
146 std::istringstream(productId) >> std::hex >> p;
147 return TryOpen(v, p, data);
151 int rc = TryOpen(DONGLE_VENDOR, DONGLE_PRODUCT);
152 DEBUG_LOG <<
"Probing dongle permissions, result: " << rc;
153 return rc == LIBUSB_ERROR_ACCESS;
157 int r = access(path, R_OK | W_OK);
159 INFO_LOG <<
"access(3) fails on: " << path <<
": " << strerror(errno);
165 static usbdata ParseUevent(std::istream& is) {
167 while (std::getline(is, line)) {
168 if (line.find(
'=') == std::string::npos) {
171 auto tokens = ocpn::split(line.c_str(),
"=");
172 if (tokens[0] !=
"PRODUCT") {
175 if (line.find(
"/") == std::string::npos) {
176 INFO_LOG <<
"invalid product line: " << line <<
"(ignored)";
179 tokens = ocpn::split(tokens[1].c_str(),
"/");
180 std::stringstream ss1;
181 ss1 << std::setfill(
'0') << std::setw(4) << tokens[0];
182 std::stringstream ss2;
183 ss2 << std::setfill(
'0') << std::setw(4) << tokens[1];
184 return usbdata(ss1.str(), ss2.str());
189 static usbdata GetDeviceUsbdata(
const char* path) {
192 int r = stat(path, &st);
194 MESSAGE_LOG <<
"Cannot stat: " << path <<
": " << strerror(errno);
197 std::stringstream syspath(
"/sys/dev/char/");
198 syspath <<
"/sys/dev/char/" << major(st.st_rdev) <<
":" << minor(st.st_rdev);
200 if (!realpath(syspath.str().c_str(), buff)) {
201 wxLogDebug(
"Error resolving link %s: %s", syspath.str().c_str(),
204 std::string real_path(buff);
207 while (real_path.length() > 0) {
208 auto uevent_path = real_path +
"/uevent";
209 if (access(uevent_path.c_str(), R_OK) >= 0) {
210 std::ifstream is(uevent_path);
211 auto data = ParseUevent(is);
214 TryOpen(data.vendor_id, data.product_id, &data);
219 size_t last_slash = real_path.rfind(
'/');
220 last_slash = last_slash == std::string::npos ? 0 : last_slash;
221 real_path = real_path.substr(0, last_slash);
226 static std::string TmpRulePath(
const char* name) {
228 getenv(
"XDG_CACHE_HOME") ? getenv(
"XDG_CACHE_HOME") :
"/tmp";
229 tmpdir +=
"/udevXXXXXX";
231 char dirpath[128] = {0};
232 strncpy(dirpath, tmpdir.c_str(),
sizeof(dirpath) - 1);
233 if (!mkdtemp(dirpath)) {
234 WARNING_LOG <<
"Cannot create tempdir: " << strerror(errno);
235 MESSAGE_LOG <<
"Using /tmp";
236 strcpy(dirpath,
"/tmp");
238 std::string path(dirpath);
245 for (
char ch : {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9'}) {
246 std::stringstream ss;
247 ss <<
"/etc/udev/rules.d/65-opencpn" << ch <<
".rules";
248 if (!ocpn::exists(ss.str())) {
249 std::string path(ss.str());
250 ocpn::replace(path,
"/etc/udev/rules.d/65-",
"");
251 ocpn::replace(path,
".rules",
"");
255 WARNING_LOG <<
"Too many opencpn device rules found (10). Giving up.";
259 static std::string CreateTmpfile(
const std::string& contents,
261 auto path = TmpRulePath(name);
262 std::ofstream of(path);
266 WARNING_LOG <<
"Cannot write to temp file: " << path;
271 static std::string CreateUdevRule(
const std::string& device,
usbdata data,
273 std::string rule(DEVICE_RULE);
274 if (device.find(
"ttyS") != std::string::npos) {
275 rule = std::string(DEVICE_RULE_TTYS);
276 auto index(device.substr(device.find(
"ttyS") + strlen(
"ttyS")));
277 ocpn::replace(rule,
"@s_index@", index);
279 ocpn::replace(rule,
"@vendor@", data.vendor_id);
280 ocpn::replace(rule,
"@product@", data.product_id);
282 ocpn::replace(rule,
"@symlink@",
symlink);
284 name.insert(0,
"65-");
286 return CreateTmpfile(rule, name.c_str());
290 std::string rule(DONGLE_RULE);
291 std::ostringstream oss;
293 oss << std::setw(4) << std::setfill(
'0') << std::hex << DONGLE_VENDOR;
294 ocpn::replace(rule,
"@vendor@", oss.str());
296 oss << std::setw(4) << std::setfill(
'0') << std::hex << DONGLE_PRODUCT;
297 ocpn::replace(rule,
"@product@", oss.str());
298 return CreateTmpfile(rule, DONGLE_RULE_NAME);
302 usbdata data = GetDeviceUsbdata(device);
303 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.