1 /*
2 * Copyright 2022, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "CanController.h"
18
19 #include "CanBusNative.h"
20 #include "CanBusSlcan.h"
21 #include "CanBusVirtual.h"
22
23 #include <android-base/format.h>
24 #include <android-base/logging.h>
25
26 #include <filesystem>
27 #include <fstream>
28 #include <regex>
29
30 namespace aidl::android::hardware::automotive::can {
31
32 namespace fs = ::std::filesystem;
33
34 namespace fsErrors {
35 static const std::error_code ok;
36 static const std::error_code eperm(EPERM, std::generic_category());
37 static const std::error_code enoent(ENOENT, std::generic_category());
38 static const std::error_code eacces(EACCES, std::generic_category());
39 } // namespace fsErrors
40
41 /* In the /sys/devices tree, there are files called "serial", which contain the serial numbers
42 * for various devices. The exact location inside of this directory is dependent upon the
43 * hardware we are running on, so we have to start from /sys/devices and work our way down. */
44 static const fs::path kDevPath("/sys/devices/");
45 static const std::regex kTtyRe("^tty[A-Z]+[0-9]+$");
46 static constexpr auto kOpts = ~(fs::directory_options::follow_directory_symlink |
47 fs::directory_options::skip_permission_denied);
48
49 constexpr auto ok = &ndk::ScopedAStatus::ok;
50
51 /**
52 * A helper object to associate the interface name and type of a USB to CAN adapter.
53 */
54 struct UsbCanIface {
55 InterfaceType iftype;
56 std::string ifaceName;
57 };
58
isValidName(const std::string & name)59 static bool isValidName(const std::string& name) {
60 static const std::regex nameRE("^[a-zA-Z0-9_]{1,32}$");
61 return std::regex_match(name, nameRE);
62 }
63
64 /**
65 * Given a path, get the last element from it.
66 *
67 * \param itrPath - the path we want the last element of
68 * \return - the last element in the path (in string form).
69 */
getLeaf(const fs::path & itrPath)70 static std::string getLeaf(const fs::path& itrPath) {
71 /* end() returns an iterator one past the leaf of the path, so we've overshot
72 decrement (--) to go back one to the leaf
73 dereference and now we have our leaf. */
74 return *(--(itrPath.end()));
75 }
76
resultToStatus(Result res,const std::string & msg="")77 static ndk::ScopedAStatus resultToStatus(Result res, const std::string& msg = "") {
78 if (msg.empty()) {
79 return ndk::ScopedAStatus(AStatus_fromServiceSpecificError(static_cast<int>(res)));
80 }
81 return ndk::ScopedAStatus(
82 AStatus_fromServiceSpecificErrorWithMessage(static_cast<int>(res), msg.c_str()));
83 }
84
85 /**
86 * Given a UsbCanIface object, get the ifaceName given the serialPath.
87 *
88 * \param serialPath - Absolute path to a "serial" file for a given device in /sys.
89 * \return A populated UsbCanIface. On failure, nullopt is returned.
90 */
getIfaceName(const fs::path & serialPath)91 static std::optional<UsbCanIface> getIfaceName(const fs::path& serialPath) {
92 std::error_code fsStatus;
93 // Since the path is to a file called "serial", we need to search its parent directory.
94 fs::recursive_directory_iterator fsItr(serialPath.parent_path(), kOpts, fsStatus);
95 if (fsStatus != fsErrors::ok) {
96 LOG(ERROR) << "Failed to open " << serialPath.parent_path();
97 return std::nullopt;
98 }
99
100 for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator();
101 fsItr.increment(fsStatus)) {
102 /* We want either a directory called "net" or a directory that looks like tty<something>, so
103 * skip files. */
104 bool isDir = fsItr->is_directory(fsStatus);
105 if (fsStatus != fsErrors::ok || !isDir) continue;
106
107 std::string currentDir = getLeaf(fsItr->path());
108 if (currentDir == "net") {
109 /* This device is a SocketCAN device. The iface name is the only directory under
110 * net/. Multiple directories under net/ is an error.*/
111 fs::directory_iterator netItr(fsItr->path(), kOpts, fsStatus);
112 if (fsStatus != fsErrors::ok) {
113 LOG(ERROR) << "Failed to open " << fsItr->path() << " to get net name!";
114 return std::nullopt;
115 }
116
117 // The leaf of our path should be the interface name.
118 std::string netName = getLeaf(netItr->path());
119
120 // Check if there is more than one item in net/
121 netItr.increment(fsStatus);
122 if (fsStatus != fsErrors::ok) {
123 // It's possible we have a valid net name, but this is most likely an error.
124 LOG(ERROR) << "Failed to verify " << fsItr->path() << " has valid net name!";
125 return std::nullopt;
126 }
127 if (netItr != fs::directory_iterator()) {
128 // There should never be more than one name under net/
129 LOG(ERROR) << "Found more than one net name in " << fsItr->path() << "!";
130 return std::nullopt;
131 }
132 return {{InterfaceType::NATIVE, netName}};
133 } else if (std::regex_match(currentDir, kTtyRe)) {
134 // This device is a USB serial device, and currentDir is the tty name.
135 return {{InterfaceType::SLCAN, "/dev/" + currentDir}};
136 }
137 }
138
139 // check if the loop above exited due to a c++fs error.
140 if (fsStatus != fsErrors::ok) {
141 LOG(ERROR) << "Failed search filesystem: " << fsStatus;
142 }
143 return std::nullopt;
144 }
145
146 /**
147 * A helper function to read the serial number from a "serial" file in /sys/devices/
148 *
149 * \param serialnoPath - path to the file to read.
150 * \return the serial number, or nullopt on failure.
151 */
readSerialNo(const std::string & serialnoPath)152 static std::optional<std::string> readSerialNo(const std::string& serialnoPath) {
153 std::ifstream serialnoStream(serialnoPath);
154 std::string serialno;
155 if (!serialnoStream.good()) {
156 LOG(ERROR) << "Failed to read serial number from " << serialnoPath;
157 return std::nullopt;
158 }
159 std::getline(serialnoStream, serialno);
160 return serialno;
161 }
162
163 /**
164 * Searches for USB devices found in /sys/devices/, and attempts to find a device matching the
165 * provided list of serial numbers.
166 *
167 * \param configSerialnos - a list of serial number (suffixes) from the HAL config.
168 * \param iftype - the type of the interface to be located.
169 * \return a matching USB device. On failure, std::nullopt is returned.
170 */
findUsbDevice(const std::vector<std::string> & configSerialnos)171 static std::optional<UsbCanIface> findUsbDevice(const std::vector<std::string>& configSerialnos) {
172 std::error_code fsStatus;
173 fs::recursive_directory_iterator fsItr(kDevPath, kOpts, fsStatus);
174 if (fsStatus != fsErrors::ok) {
175 LOG(ERROR) << "Failed to open " << kDevPath;
176 return std::nullopt;
177 }
178
179 for (; fsStatus == fsErrors::ok && fsItr != fs::recursive_directory_iterator();
180 fsItr.increment(fsStatus)) {
181 // We want to find a file called "serial", which is in a directory somewhere. Skip files.
182 bool isDir = fsItr->is_directory(fsStatus);
183 if (fsStatus != fsErrors::ok) {
184 LOG(ERROR) << "Failed check if " << fsStatus;
185 return std::nullopt;
186 }
187 if (!isDir) continue;
188
189 auto serialnoPath = fsItr->path() / "serial";
190 bool isReg = fs::is_regular_file(serialnoPath, fsStatus);
191
192 /* Make sure we have permissions to this directory, ignore enoent, since the file
193 * "serial" may not exist, which is ok. */
194 if (fsStatus == fsErrors::eperm || fsStatus == fsErrors::eacces) {
195 /* This means we don't have access to this directory. If we recurse into it, this
196 * will cause the iterator to loose its state and we'll crash. */
197 fsItr.disable_recursion_pending();
198 continue;
199 }
200 if (fsStatus == fsErrors::enoent) continue;
201 if (fsStatus != fsErrors::ok) {
202 LOG(WARNING) << "An unexpected error occurred while checking for serialno: "
203 << fsStatus;
204 continue;
205 }
206 if (!isReg) continue;
207
208 // we found a serial number
209 auto serialno = readSerialNo(serialnoPath);
210 if (!serialno.has_value()) continue;
211
212 // see if the serial number exists in the config
213 for (auto&& cfgSn : configSerialnos) {
214 if (serialno->ends_with(std::string(cfgSn))) {
215 auto ifaceInfo = getIfaceName(serialnoPath);
216 if (!ifaceInfo.has_value()) break;
217 return ifaceInfo;
218 }
219 }
220 }
221 if (fsStatus != fsErrors::ok) {
222 LOG(ERROR) << "Error searching filesystem: " << fsStatus;
223 return std::nullopt;
224 }
225 return std::nullopt;
226 }
227
getSupportedInterfaceTypes(std::vector<InterfaceType> * supportedTypes)228 ndk::ScopedAStatus CanController::getSupportedInterfaceTypes(
229 std::vector<InterfaceType>* supportedTypes) {
230 *supportedTypes = {InterfaceType::VIRTUAL, InterfaceType::NATIVE, InterfaceType::SLCAN};
231 return ok();
232 }
233
getInterfaceName(const std::string & busName,std::string * ifaceName)234 ndk::ScopedAStatus CanController::getInterfaceName(const std::string& busName,
235 std::string* ifaceName) {
236 *ifaceName = {};
237 if (mBusesByName.find(busName) == mBusesByName.end()) {
238 return resultToStatus(Result::BAD_BUS_NAME, fmt::format("{} doesn't exist", busName));
239 }
240 *ifaceName = std::string(mBusesByName[busName]->getIfaceName());
241 return ok();
242 }
243
upBus(const BusConfig & config,std::string * ifaceName)244 ndk::ScopedAStatus CanController::upBus(const BusConfig& config, std::string* ifaceName) {
245 if (!isValidName(config.name)) {
246 LOG(ERROR) << "Bus name " << config.name << " is invalid";
247 return resultToStatus(Result::BAD_BUS_NAME,
248 fmt::format("{} is not a valid bus name", config.name));
249 } else if (mBusesByName.find(config.name) != mBusesByName.end()) {
250 LOG(ERROR) << "A bus named " << config.name << " already exists!";
251 return resultToStatus(Result::INVALID_STATE,
252 fmt::format("A bus named {} already exists", config.name));
253 }
254
255 if (config.interfaceId.getTag() == BusConfig::InterfaceId::Tag::virtualif) {
256 auto& virtualif = config.interfaceId.get<BusConfig::InterfaceId::Tag::virtualif>();
257 mBusesByName[config.name] = std::make_unique<CanBusVirtual>(virtualif.ifname);
258 }
259
260 else if (config.interfaceId.getTag() == BusConfig::InterfaceId::Tag::nativeif) {
261 auto& nativeif = config.interfaceId.get<BusConfig::InterfaceId::Tag::nativeif>();
262 std::string ifaceName;
263 if (nativeif.interfaceId.getTag() == NativeInterface::InterfaceId::Tag::serialno) {
264 // Configure by serial number.
265 auto selectedDevice = findUsbDevice(
266 nativeif.interfaceId.get<NativeInterface::InterfaceId::Tag::serialno>());
267 // verify the returned device is the correct one
268 if (!selectedDevice.has_value() || selectedDevice->iftype != InterfaceType::NATIVE) {
269 return resultToStatus(
270 Result::BAD_INTERFACE_ID,
271 "Couldn't find a native socketcan device with the given serial number(s)");
272 }
273 ifaceName = selectedDevice->ifaceName;
274 } else {
275 // configure by iface name.
276 ifaceName = nativeif.interfaceId.get<NativeInterface::InterfaceId::Tag::ifname>();
277 }
278 mBusesByName[config.name] = std::make_unique<CanBusNative>(ifaceName, config.bitrate);
279 }
280
281 else if (config.interfaceId.getTag() == BusConfig::InterfaceId::Tag::slcan) {
282 auto& slcanif = config.interfaceId.get<BusConfig::InterfaceId::Tag::slcan>();
283 std::string ttyName;
284 if (slcanif.interfaceId.getTag() == SlcanInterface::InterfaceId::Tag::serialno) {
285 // Configure by serial number.
286 auto selectedDevice = findUsbDevice(
287 slcanif.interfaceId.get<SlcanInterface::InterfaceId::Tag::serialno>());
288 if (!selectedDevice.has_value() || selectedDevice->iftype != InterfaceType::SLCAN) {
289 return resultToStatus(
290 Result::BAD_INTERFACE_ID,
291 "Couldn't find a slcan device with the given serial number(s)");
292 }
293 ttyName = selectedDevice->ifaceName;
294 } else {
295 // Configure by tty name.
296 ttyName = slcanif.interfaceId.get<SlcanInterface::InterfaceId::Tag::ttyname>();
297 }
298 mBusesByName[config.name] = std::make_unique<CanBusSlcan>(ttyName, config.bitrate);
299 }
300
301 else if (config.interfaceId.getTag() == BusConfig::InterfaceId::Tag::indexed) {
302 return resultToStatus(Result::NOT_SUPPORTED,
303 "Indexed devices are not supported in this implementation");
304 } else {
305 // this shouldn't happen.
306 return resultToStatus(Result::UNKNOWN_ERROR, "Unknown interface id type");
307 }
308
309 Result result = mBusesByName[config.name]->up();
310 if (result != Result::OK) {
311 // the bus failed to come up, don't leave a broken entry in the map.
312 mBusesByName.erase(config.name);
313 return resultToStatus(result, fmt::format("CanBus::up failed for {}", config.name));
314 }
315
316 *ifaceName = mBusesByName[config.name]->getIfaceName();
317 return ok();
318 }
319
downBus(const std::string & busName)320 ndk::ScopedAStatus CanController::downBus(const std::string& busName) {
321 if (mBusesByName.find(busName) == mBusesByName.end()) {
322 return resultToStatus(
323 Result::UNKNOWN_ERROR,
324 fmt::format("Couldn't bring down {}, because it doesn't exist", busName));
325 }
326 Result result = mBusesByName[busName]->down();
327 if (result != Result::OK) {
328 return resultToStatus(result, fmt::format("Couldn't bring down {}!", busName));
329 }
330 mBusesByName.erase(busName);
331 return ok();
332 }
333 } // namespace aidl::android::hardware::automotive::can
334