OpenCPN Partial API docs
Loading...
Searching...
No Matches
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
25#include "config.h"
26
27#include <string>
28#include <sstream>
29#include <iomanip>
30#include <iostream>
31
32#include <stdlib.h>
33
34#ifndef HAVE_UNISTD_H
35#error linux_devices requires unistd.h to be available
36#endif
37#include <unistd.h>
38
39#include <sys/sysmacros.h>
40#include <sys/stat.h>
41
42#ifndef HAVE_LIBUSB_10
43#error linux_devices requires libusb-1.0 to be available
44#endif
45#include <libusb.h>
46
47#include "model/linux_devices.h"
48#include "model/logger.h"
49#include "model/ocpn_utils.h"
50
51typedef struct usbdata {
52 std::string vendor_id;
53 std::string product_id;
54 std::string vendor;
55 std::string product;
56 std::string serial_nr;
57
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; }
61} usbdata;
62
63static const int DONGLE_VENDOR = 0x1547;
64static const int DONGLE_PRODUCT = 0x1000;
65
66static const char* const DONGLE_RULE = R"--(
67ATTRS{idVendor}=="@vendor@", ATTRS{idProduct}=="@product@", MODE="0666"
68)--";
69
70static const char* const DEVICE_RULE = R"--(
71ATTRS{idVendor}=="@vendor@", ATTRS{idProduct}=="@product@", \
72 MODE="0666", SYMLINK+="@symlink@"
73)--";
74
75static const char* const DEVICE_RULE_TTYS = R"--(
76KERNEL=="ttyS@s_index@", MODE="0666"
77)--";
78
79static const char* const DONGLE_RULE_NAME = "65-ocpn-dongle.rules";
80
82static void ReadUsbdata(libusb_device* dev, libusb_device_handle* handle,
83 usbdata* data) {
84 struct libusb_device_descriptor desc;
85 libusb_get_device_descriptor(dev, &desc);
86
87 unsigned char buff[256];
88 int r;
89 if (desc.iProduct) {
90 r = libusb_get_string_descriptor_ascii(handle, desc.iProduct, buff,
91 sizeof(buff));
92 if (r > 0) data->product = reinterpret_cast<char*>(buff);
93 }
94 if (desc.iManufacturer) {
95 r = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, buff,
96 sizeof(buff));
97 if (r > 0) data->vendor = reinterpret_cast<char*>(buff);
98 }
99 if (desc.iSerialNumber) {
100 r = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, buff,
101 sizeof(buff));
102 if (r > 0) data->serial_nr = reinterpret_cast<char*>(buff);
103 }
104}
105
106static int TryOpen(int vendorId, int productId, usbdata* data = 0) {
107 libusb_context* ctx = 0;
108 int r = libusb_init(&ctx);
109 if (r != 0) {
110 auto e = static_cast<libusb_error>(r);
111 WARNING_LOG << "Cannot initialize libusb: " << libusb_strerror(e);
112 return LIBUSB_ERROR_NOT_SUPPORTED;
113 }
114 libusb_device** device_list;
115 ssize_t size = libusb_get_device_list(ctx, &device_list);
116 if (size < 0) {
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;
120 }
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) {
126 continue;
127 }
128 libusb_device_handle* dev_handle;
129 r = libusb_open(*dev, &dev_handle);
130 if (r >= 0) {
131 if (data) {
132 ReadUsbdata(*dev, dev_handle, data);
133 }
134 libusb_close(dev_handle);
135 }
136 break;
137 }
138 libusb_free_device_list(device_list, 1);
139 DEBUG_LOG << "Nothing found for " << vendorId << ":" << productId;
140 libusb_exit(0);
141 return r;
142}
143
144static int TryOpen(const std::string vendorId, const std::string productId,
145 usbdata* data = 0) {
146 int v;
147 int p;
148 std::istringstream(vendorId) >> std::hex >> v;
149 std::istringstream(productId) >> std::hex >> p;
150 return TryOpen(v, p, data);
151}
152
154 int rc = TryOpen(DONGLE_VENDOR, DONGLE_PRODUCT);
155 DEBUG_LOG << "Probing dongle permissions, result: " << rc;
156 return rc == LIBUSB_ERROR_ACCESS;
157}
158
159bool IsDevicePermissionsOk(const char* path) {
160 int r = access(path, R_OK | W_OK);
161 if (r < 0) {
162 INFO_LOG << "access(3) fails on: " << path << ": " << strerror(errno);
163 }
164 return r == 0;
165}
166
168static usbdata ParseUevent(std::istream& is) {
169 std::string line;
170 while (std::getline(is, line)) {
171 if (line.find('=') == std::string::npos) {
172 continue;
173 }
174 auto tokens = ocpn::split(line.c_str(), "=");
175 if (tokens[0] != "PRODUCT") {
176 continue;
177 }
178 if (line.find("/") == std::string::npos) {
179 INFO_LOG << "invalid product line: " << line << "(ignored)";
180 continue;
181 }
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());
188 }
189 return usbdata("", "");
190}
191
192static usbdata GetDeviceUsbdata(const char* path) {
193 // Get real path for node in /sys corresponding to path in /dev
194 struct stat st;
195 int r = stat(path, &st);
196 if (r < 0) {
197 MESSAGE_LOG << "Cannot stat: " << path << ": " << strerror(errno);
198 return usbdata(0, 0);
199 }
200 std::stringstream syspath("/sys/dev/char/");
201 syspath << "/sys/dev/char/" << major(st.st_rdev) << ":" << minor(st.st_rdev);
202 char buff[PATH_MAX];
203 if (!realpath(syspath.str().c_str(), buff)) {
204 wxLogDebug("Error resolving link %s: %s", syspath.str().c_str(),
205 strerror(errno));
206 }
207 std::string real_path(buff);
208
209 // Get the uevent file in each parent dir and parse it.
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);
215 if (data.is_ok()) {
216 // Add missing pieces (descriptions...) using libusb
217 TryOpen(data.vendor_id, data.product_id, &data);
218 return data;
219 }
220 }
221 // Drop last part of filename
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);
225 }
226 return usbdata("", "");
227}
228
229static std::string TmpRulePath(const char* name) {
230 std::string tmpdir =
231 getenv("XDG_CACHE_HOME") ? getenv("XDG_CACHE_HOME") : "/tmp";
232 tmpdir += "/udevXXXXXX";
233
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");
240 }
241 std::string path(dirpath);
242 path += "/";
243 path += name;
244 return path;
245}
246
247std::string MakeUdevLink() {
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", "");
255 return path;
256 }
257 }
258 WARNING_LOG << "Too many opencpn device rules found (10). Giving up.";
259 return "";
260}
261
262static std::string CreateTmpfile(const std::string& contents,
263 const char* name) {
264 auto path = TmpRulePath(name);
265 std::ofstream of(path);
266 of << contents;
267 of.close();
268 if (of.bad()) {
269 WARNING_LOG << "Cannot write to temp file: " << path;
270 }
271 return path;
272}
273
274static std::string CreateUdevRule(const std::string& device, usbdata data,
275 const char* symlink) {
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);
281 } else {
282 ocpn::replace(rule, "@vendor@", data.vendor_id);
283 ocpn::replace(rule, "@product@", data.product_id);
284 }
285 ocpn::replace(rule, "@symlink@", symlink);
286 std::string name(symlink);
287 name.insert(0, "65-");
288 name += ".rules";
289 return CreateTmpfile(rule, name.c_str());
290}
291
292std::string GetDongleRule() {
293 std::string rule(DONGLE_RULE);
294 std::ostringstream oss;
295
296 oss << std::setw(4) << std::setfill('0') << std::hex << DONGLE_VENDOR;
297 ocpn::replace(rule, "@vendor@", oss.str());
298 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);
302}
303
304std::string GetDeviceRule(const char* device, const char* symlink) {
305 usbdata data = GetDeviceUsbdata(device);
306 auto path = CreateUdevRule(device, data, symlink);
307 return path;
308}
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.