OpenCPN Partial API docs
linux_devices.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * Copyright (C) 2011 - 2024 Alec Leamas *
3  * *
4  * This program is free software; you can redistribute it and/or modify *
5  * it under the terms of the GNU General Public License as published by *
6  * the Free Software Foundation; either version 2 of the License, or *
7  * (at your option) any later version. *
8  * *
9  * This program is distributed in the hope that it will be useful, *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12  * GNU General Public License for more details. *
13  * *
14  * You should have received a copy of the GNU General Public License *
15  * along with this program; if not, write to the *
16  * Free Software Foundation, Inc., *
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
18  **************************************************************************/
19 
22 #include "config.h"
23 
24 #include <string>
25 #include <sstream>
26 #include <iomanip>
27 #include <iostream>
28 
29 #include <stdlib.h>
30 
31 #ifndef HAVE_UNISTD_H
32 #error linux_devices requires unistd.h to be available
33 #endif
34 #include <unistd.h>
35 
36 #include <sys/sysmacros.h>
37 #include <sys/stat.h>
38 
39 #ifndef HAVE_LIBUSB_10
40 #error linux_devices requires libusb-1.0 to be available
41 #endif
42 #include <libusb.h>
43 
44 #include "model/linux_devices.h"
45 #include "model/logger.h"
46 #include "model/ocpn_utils.h"
47 
48 typedef struct usbdata {
49  std::string vendor_id;
50  std::string product_id;
51  std::string vendor;
52  std::string product;
53  std::string serial_nr;
54 
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; }
58 } usbdata;
59 
60 static const int DONGLE_VENDOR = 0x1547;
61 static const int DONGLE_PRODUCT = 0x1000;
62 
63 static const char* const DONGLE_RULE = R"--(
64 ATTRS{idVendor}=="@vendor@", ATTRS{idProduct}=="@product@", MODE="0666"
65 )--";
66 
67 static const char* const DEVICE_RULE = R"--(
68 ATTRS{idVendor}=="@vendor@", ATTRS{idProduct}=="@product@", \
69  MODE="0666", SYMLINK+="@symlink@"
70 )--";
71 
72 static const char* const DEVICE_RULE_TTYS = R"--(
73 KERNEL=="ttyS@s_index@", MODE="0666"
74 )--";
75 
76 static const char* const DONGLE_RULE_NAME = "65-ocpn-dongle.rules";
77 
79 static void ReadUsbdata(libusb_device* dev, libusb_device_handle* handle,
80  usbdata* data) {
81  struct libusb_device_descriptor desc;
82  libusb_get_device_descriptor(dev, &desc);
83 
84  unsigned char buff[256];
85  int r;
86  if (desc.iProduct) {
87  r = libusb_get_string_descriptor_ascii(handle, desc.iProduct, buff,
88  sizeof(buff));
89  if (r > 0) data->product = reinterpret_cast<char*>(buff);
90  }
91  if (desc.iManufacturer) {
92  r = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, buff,
93  sizeof(buff));
94  if (r > 0) data->vendor = reinterpret_cast<char*>(buff);
95  }
96  if (desc.iSerialNumber) {
97  r = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, buff,
98  sizeof(buff));
99  if (r > 0) data->serial_nr = reinterpret_cast<char*>(buff);
100  }
101 }
102 
103 static int TryOpen(int vendorId, int productId, usbdata* data = 0) {
104  libusb_context* ctx = 0;
105  int r = libusb_init(&ctx);
106  if (r != 0) {
107  auto e = static_cast<libusb_error>(r);
108  WARNING_LOG << "Cannot initialize libusb: " << libusb_strerror(e);
109  return LIBUSB_ERROR_NOT_SUPPORTED;
110  }
111  libusb_device** device_list;
112  ssize_t size = libusb_get_device_list(ctx, &device_list);
113  if (size < 0) {
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;
117  }
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) {
123  continue;
124  }
125  libusb_device_handle* dev_handle;
126  r = libusb_open(*dev, &dev_handle);
127  if (r >= 0) {
128  if (data) {
129  ReadUsbdata(*dev, dev_handle, data);
130  }
131  libusb_close(dev_handle);
132  }
133  break;
134  }
135  libusb_free_device_list(device_list, 1);
136  DEBUG_LOG << "Nothing found for " << vendorId << ":" << productId;
137  libusb_exit(0);
138  return r;
139 }
140 
141 static int TryOpen(const std::string vendorId, const std::string productId,
142  usbdata* data = 0) {
143  int v;
144  int p;
145  std::istringstream(vendorId) >> std::hex >> v;
146  std::istringstream(productId) >> std::hex >> p;
147  return TryOpen(v, p, data);
148 }
149 
151  int rc = TryOpen(DONGLE_VENDOR, DONGLE_PRODUCT);
152  DEBUG_LOG << "Probing dongle permissions, result: " << rc;
153  return rc == LIBUSB_ERROR_ACCESS;
154 }
155 
156 bool IsDevicePermissionsOk(const char* path) {
157  int r = access(path, R_OK | W_OK);
158  if (r < 0) {
159  INFO_LOG << "access(3) fails on: " << path << ": " << strerror(errno);
160  }
161  return r == 0;
162 }
163 
165 static usbdata ParseUevent(std::istream& is) {
166  std::string line;
167  while (std::getline(is, line)) {
168  if (line.find('=') == std::string::npos) {
169  continue;
170  }
171  auto tokens = ocpn::split(line.c_str(), "=");
172  if (tokens[0] != "PRODUCT") {
173  continue;
174  }
175  if (line.find("/") == std::string::npos) {
176  INFO_LOG << "invalid product line: " << line << "(ignored)";
177  continue;
178  }
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());
185  }
186  return usbdata("", "");
187 }
188 
189 static usbdata GetDeviceUsbdata(const char* path) {
190  // Get real path for node in /sys corresponding to path in /dev
191  struct stat st;
192  int r = stat(path, &st);
193  if (r < 0) {
194  MESSAGE_LOG << "Cannot stat: " << path << ": " << strerror(errno);
195  return usbdata(0, 0);
196  }
197  std::stringstream syspath("/sys/dev/char/");
198  syspath << "/sys/dev/char/" << major(st.st_rdev) << ":" << minor(st.st_rdev);
199  char buff[PATH_MAX];
200  if (!realpath(syspath.str().c_str(), buff)) {
201  wxLogDebug("Error resolving link %s: %s", syspath.str().c_str(),
202  strerror(errno));
203  }
204  std::string real_path(buff);
205 
206  // Get the uevent file in each parent dir and parse it.
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);
212  if (data.is_ok()) {
213  // Add missing pieces (descriptions...) using libusb
214  TryOpen(data.vendor_id, data.product_id, &data);
215  return data;
216  }
217  }
218  // Drop last part of filename
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);
222  }
223  return usbdata("", "");
224 }
225 
226 static std::string TmpRulePath(const char* name) {
227  std::string tmpdir =
228  getenv("XDG_CACHE_HOME") ? getenv("XDG_CACHE_HOME") : "/tmp";
229  tmpdir += "/udevXXXXXX";
230 
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");
237  }
238  std::string path(dirpath);
239  path += "/";
240  path += name;
241  return path;
242 }
243 
244 std::string MakeUdevLink() {
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", "");
252  return path;
253  }
254  }
255  WARNING_LOG << "Too many opencpn device rules found (10). Giving up.";
256  return "";
257 }
258 
259 static std::string CreateTmpfile(const std::string& contents,
260  const char* name) {
261  auto path = TmpRulePath(name);
262  std::ofstream of(path);
263  of << contents;
264  of.close();
265  if (of.bad()) {
266  WARNING_LOG << "Cannot write to temp file: " << path;
267  }
268  return path;
269 }
270 
271 static std::string CreateUdevRule(const std::string& device, usbdata data,
272  const char* symlink) {
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);
278  } else {
279  ocpn::replace(rule, "@vendor@", data.vendor_id);
280  ocpn::replace(rule, "@product@", data.product_id);
281  }
282  ocpn::replace(rule, "@symlink@", symlink);
283  std::string name(symlink);
284  name.insert(0, "65-");
285  name += ".rules";
286  return CreateTmpfile(rule, name.c_str());
287 }
288 
289 std::string GetDongleRule() {
290  std::string rule(DONGLE_RULE);
291  std::ostringstream oss;
292 
293  oss << std::setw(4) << std::setfill('0') << std::hex << DONGLE_VENDOR;
294  ocpn::replace(rule, "@vendor@", oss.str());
295  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);
299 }
300 
301 std::string GetDeviceRule(const char* device, const char* symlink) {
302  usbdata data = GetDeviceUsbdata(device);
303  auto path = CreateUdevRule(device, data, symlink);
304  return path;
305 }
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.