OpenCPN Partial API docs
Loading...
Searching...
No Matches
linux_devices.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: Low-level USB device management
5 * Author: Alec Leamas
6 *
7 ***************************************************************************
8 * Copyright (C) 2011 Alec Leamas *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
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 "linux_devices.h"
48#include "logger.h"
49#include "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(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; }
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 DONGLE_RULE_NAME = "65-ocpn-dongle.rules";
76
77static void read_usbdata(libusb_device* dev, libusb_device_handle* handle,
78 usbdata* data) {
79 struct libusb_device_descriptor desc;
80 libusb_get_device_descriptor(dev, &desc);
81
82 std::stringstream ss;
83 ss << std::setfill('0') << std::setw(4) << desc.idVendor;
84 data->vendor_id = std::string(ss.str());
85 ss.str("");
86 ss << std::setfill('0') << std::setw(4) << desc.idProduct;
87 data->vendor_id = std::string(ss.str());
88
89 unsigned char buff[256];
90 int r;
91 if (desc.iProduct) {
92 r = libusb_get_string_descriptor_ascii(handle, desc.iProduct, buff,
93 sizeof(buff));
94 if (r > 0) data->product = reinterpret_cast<char*>(buff);
95 }
96 if (desc.iManufacturer) {
97 r = libusb_get_string_descriptor_ascii(handle, desc.iManufacturer, buff,
98 sizeof(buff));
99 if (r > 0) data->vendor = reinterpret_cast<char*>(buff);
100 }
101 if (desc.iSerialNumber) {
102 r = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, buff,
103 sizeof(buff));
104 if (r > 0) data->serial_nr = reinterpret_cast<char*>(buff);
105 }
106}
107
108static int try_open(int vendorId, int productId, usbdata* data = 0) {
109 libusb_context* ctx = 0;
110 int r = libusb_init(&ctx);
111 if (r != 0) {
112 auto e = static_cast<libusb_error>(r);
113 WARNING_LOG << "Cannot initialize libusb: " << libusb_strerror(e);
114 return LIBUSB_ERROR_NOT_SUPPORTED;
115 }
116 libusb_device** device_list;
117 ssize_t size = libusb_get_device_list(ctx, &device_list);
118 if (size < 0) {
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;
122 }
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) {
128 continue;
129 }
130 libusb_device_handle* dev_handle;
131 r = libusb_open(*dev, &dev_handle);
132 if (r >= 0) {
133 if (data) {
134 read_usbdata(*dev, dev_handle, data);
135 }
136 libusb_close(dev_handle);
137 }
138 break;
139 }
140 libusb_free_device_list(device_list, 1);
141 DEBUG_LOG << "Nothing found for " << vendorId << ":" << productId;
142 libusb_exit(0);
143 return r;
144}
145
146static int try_open(const std::string vendorId, const std::string productId,
147 usbdata* data = 0) {
148 int v;
149 int p;
150 std::istringstream(vendorId) >> std::hex >> v;
151 std::istringstream(productId) >> std::hex >> p;
152 return try_open(v, p, data);
153}
154
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;
159}
160
161bool is_device_permissions_ok(const char* path) {
162 int r = access(path, R_OK | W_OK);
163 if (r < 0) {
164 INFO_LOG << "access(3) fails on: " << path << ": " << strerror(errno);
165 }
166 return r == 0;
167}
168
170static usbdata parse_uevent(std::istream& is) {
171 std::string line;
172 while (std::getline(is, line)) {
173 if (line.find('=') == std::string::npos) {
174 continue;
175 }
176 auto tokens = ocpn::split(line.c_str(), "=");
177 if (tokens[0] != "PRODUCT") {
178 continue;
179 }
180 if (line.find("/") == std::string::npos) {
181 INFO_LOG << "invalid product line: " << line << "(ignored)";
182 continue;
183 }
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());
190 }
191 return usbdata("", "");
192}
193
194static usbdata get_device_usbdata(const char* path) {
195 // Get real path for node in /sys corresponding to path in /dev
196 struct stat st;
197 int r = stat(path, &st);
198 if (r < 0) {
199 MESSAGE_LOG << "Cannot stat: " << path << ": " << strerror(errno);
200 return usbdata(0, 0);
201 }
202 std::stringstream syspath("/sys/dev/char/");
203 syspath << "/sys/dev/char/" << major(st.st_rdev) << ":" << minor(st.st_rdev);
204 char buff[PATH_MAX];
205 realpath(syspath.str().c_str(), buff);
206 std::string real_path(buff);
207
208 // Get the uevent file in each parent dir and parse it.
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);
214 if (data.is_ok()) {
215 // Add missing pieces (descriptions...) using libusb
216 try_open(data.vendor_id, data.product_id, &data);
217 return data;
218 }
219 }
220 // Drop last part of filename
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);
224 }
225 return usbdata("", "");
226}
227
228static std::string tmp_rule_path(const char* name) {
229 std::string tmpdir =
230 getenv("XDG_CACHE_HOME") ? getenv("XDG_CACHE_HOME") : "/tmp";
231 tmpdir += "/udevXXXXXX";
232
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");
239 }
240 std::string path(dirpath);
241 path += "/";
242 path += name;
243 return path;
244}
245
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");
249 path += ch;
250 if (!ocpn::exists(path)) {
251 ocpn::replace(path, "/dev/", "");
252 return path;
253 }
254 }
255 WARNING_LOG << "Too many opencpn devices found (10). Giving up.";
256 return "";
257}
258
259static std::string create_tmpfile(const std::string& contents,
260 const char* name) {
261 auto path = tmp_rule_path(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
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);
276
277 std::string name(symlink);
278 name.insert(0, "65-");
279 name += ".rules";
280 return create_tmpfile(rule, name.c_str());
281}
282
283std::string get_dongle_rule() {
284 std::string rule(DONGLE_RULE);
285 std::ostringstream oss;
286
287 oss << std::setw(4) << std::setfill('0') << std::hex << DONGLE_VENDOR;
288 ocpn::replace(rule, "@vendor@", oss.str());
289 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);
293}
294
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);
298 return path;
299}