1 /*
2  * This file is part of the libCEC(R) library.
3  *
4  * libCEC(R) is Copyright (C) 2011-2015 Pulse-Eight Limited.  All rights reserved.
5  * libCEC(R) is an original work, containing original code.
6  *
7  * libCEC(R) is a trademark of Pulse-Eight Limited.
8  *
9  * This program is dual-licensed; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301  USA
23  *
24  *
25  * Alternatively, you can license this library under a commercial license,
26  * please contact Pulse-Eight Licensing for more information.
27  *
28  * For more information contact:
29  * Pulse-Eight Licensing       <license@pulse-eight.com>
30  *     http://www.pulse-eight.com/
31  *     http://www.pulse-eight.net/
32  */
33 
34 #include "env.h"
35 #include "USBCECAdapterDetection.h"
36 
37 #if defined(__APPLE__)
38 #include <dirent.h>
39 #include <sys/param.h>
40 #include <IOKit/IOKitLib.h>
41 #include <IOKit/IOMessage.h>
42 #include <IOKit/IOCFPlugIn.h>
43 #include <IOKit/usb/IOUSBLib.h>
44 #include <IOKit/serial/IOSerialKeys.h>
45 #include <CoreFoundation/CoreFoundation.h>
46 #elif defined(__WINDOWS__)
47 #pragma comment(lib, "setupapi.lib")
48 #pragma comment(lib, "cfgmgr32.lib")
49 #include <setupapi.h>
50 #include <cfgmgr32.h>
51 #include <tchar.h>
52 
53 // the virtual COM port only shows up when requesting devices with the raw device guid!
54 static GUID USB_RAW_GUID = { 0xA5DCBF10, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } };
55 static GUID USB_CDC_GUID = { 0x4D36E978, 0xE325, 0x11CE, { 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 } };
56 
57 #elif defined(HAVE_LIBUDEV)
58 #if defined(__FreeBSD__)
59 #include <sys/sysctl.h>
60 #endif
61 #include <dirent.h>
62 #include <poll.h>
63 extern "C" {
64 #include <libudev.h>
65 }
66 #elif defined(__FreeBSD__) || defined(__DragonFly__)
67 #include <sys/param.h>
68 #include <sys/sysctl.h>
69 #include <stdio.h>
70 #include <unistd.h>
71 #endif
72 
73 #if defined(__linux__)
74 #include <dirent.h>
75 #include <ios>
76 #include <fstream>
77 #endif
78 
79 #include <string>
80 #include <algorithm>
81 #include <stdio.h>
82 #include "p8-platform/util/StringUtils.h"
83 
84 #define CEC_VID  0x2548
85 #define CEC_PID  0x1001
86 #define CEC_PID2 0x1002
87 
88 using namespace CEC;
89 
90 #if defined(HAVE_LIBUDEV) || defined(__linux__)
TranslateComPort(std::string & strString)91 bool TranslateComPort(std::string& strString)
92 {
93   std::string strTmp(strString);
94   std::reverse(strTmp.begin(), strTmp.end());
95   const char* iSlash = strchr(strTmp.c_str(), '/');
96   if (iSlash)
97   {
98     strTmp = StringUtils::Left(strTmp, iSlash - strTmp.c_str());
99     std::reverse(strTmp.begin(), strTmp.end());
100     strString = StringUtils::Format("%s/%s:1.0/tty", strString.c_str(), strTmp.c_str());
101     return true;
102   }
103 
104   return false;
105 }
106 
FindComPort(std::string & strLocation)107 bool FindComPort(std::string& strLocation)
108 {
109   std::string strPort = strLocation;
110   bool bReturn(!strPort.empty());
111   std::string strConfigLocation(strLocation);
112   if (TranslateComPort(strConfigLocation))
113   {
114     DIR *dir;
115     struct dirent *dirent;
116     if((dir = opendir(strConfigLocation.c_str())) == NULL)
117       return bReturn;
118 
119     while ((dirent = readdir(dir)) != NULL)
120     {
121       if(strcmp((char*)dirent->d_name, "." ) != 0 && strcmp((char*)dirent->d_name, ".." ) != 0)
122       {
123         strPort = StringUtils::Format("/dev/%s", dirent->d_name);
124         if (!strPort.empty())
125         {
126           strLocation = strPort;
127           bReturn = true;
128           break;
129         }
130       }
131     }
132     closedir(dir);
133   }
134 
135   return bReturn;
136 }
137 #endif
138 
CanAutodetect(void)139 bool CUSBCECAdapterDetection::CanAutodetect(void)
140 {
141 #if defined(__APPLE__) || defined(HAVE_LIBUDEV) || defined(__WINDOWS__) || defined(__FreeBSD__) || defined(__linux__) || defined(__DragonFly__)
142   return true;
143 #else
144   return false;
145 #endif
146 }
147 
148 #if defined(__WINDOWS__)
149 static DEVPROPKEY ADAPTER_LOCATION = { 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14 };
150 
GetComPortFromDevNode(DEVINST hDevInst,char * strPortName,unsigned int iSize)151 static bool GetComPortFromDevNode(DEVINST hDevInst, char* strPortName, unsigned int iSize)
152 {
153   WCHAR friendlyName[256];
154   WCHAR* portLocation;
155   DEVPROPTYPE PropertyType;
156   ULONG PropertySize;
157 
158   // grab the com port from the device's friendly name
159   PropertySize = sizeof(friendlyName);
160   CM_Get_DevNode_PropertyW(hDevInst, &ADAPTER_LOCATION, &PropertyType, (PBYTE)friendlyName, &PropertySize, 0);
161   if (!!(portLocation = wcsstr(friendlyName, L"COM")))
162   {
163     std::string port;
164     char narrow[6];
165     size_t end;
166     snprintf(narrow, 6, "%ws", portLocation);
167     port = std::string(narrow);
168     if ((end = port.find(")")) != std::string::npos)
169     {
170       port = port.substr(0, end);
171       strncpy(strPortName, port.c_str(), iSize);
172       return true;
173     }
174   }
175 
176   return false;
177 }
178 
GetPidVidFromDeviceName(const std::string strDevName,int * vid,int * pid)179 static bool GetPidVidFromDeviceName(const std::string strDevName, int* vid, int* pid)
180 {
181   std::string strDevNameUpper(strDevName);
182   StringUtils::ToUpper(strDevNameUpper);
183   size_t iPidPos = strDevNameUpper.find("PID_");
184   size_t iVidPos = strDevNameUpper.find("VID_");
185   if (iPidPos == std::string::npos || iVidPos == std::string::npos || (strDevNameUpper.find("&MI_") != std::string::npos && strDevNameUpper.find("&MI_00") == std::string::npos))
186     return false;
187 
188   std::string strVendorId(strDevNameUpper.substr(iVidPos + 4, 4));
189   std::string strProductId(strDevNameUpper.substr(iPidPos + 4, 4));
190 
191   sscanf(strVendorId.c_str(), "%x", vid);
192   sscanf(strProductId.c_str(), "%x", pid);
193 
194   return true;
195 }
196 #endif
197 
FindAdaptersWindows(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)198 uint8_t CUSBCECAdapterDetection::FindAdaptersWindows(cec_adapter_descriptor* deviceList, uint8_t iBufSize, const char* strDevicePath /* = NULL */)
199 {
200   uint8_t iFound(0);
201 
202 #if defined(__WINDOWS__)
203   ULONG len;
204   PCHAR buffer;
205 
206   CM_Get_Device_ID_List_Size(&len, 0, CM_GETIDLIST_FILTER_NONE);
207   buffer = (PCHAR)malloc(sizeof(CHAR) * len);
208   if (buffer)
209   {
210     CM_Get_Device_ID_List(0, buffer, len, CM_GETIDLIST_FILTER_NONE);
211 
212     for (CHAR* devId = buffer; *devId; devId += strlen(devId) + 1)
213     {
214       // check whether the path matches, if a path was given
215       if (strDevicePath && strcmp(strDevicePath, devId) != 0)
216         continue;
217 
218       // get the vid and pid
219       int iVendor, iProduct;
220       if (!GetPidVidFromDeviceName(devId, &iVendor, &iProduct))
221         continue;
222 
223       // no match
224       if (iVendor != CEC_VID || (iProduct != CEC_PID && iProduct != CEC_PID2))
225         continue;
226 
227       // locate the device node
228       DEVINST devInst = 0, childInst = 0;
229       if (CM_Locate_DevNode(&devInst, devId, 0) != CR_SUCCESS)
230         continue;
231 
232       // get the child node if this is a composite device
233       if (iProduct == CEC_PID2)
234       {
235         if (CM_Get_Child(&childInst, devInst, 0) != CR_SUCCESS)
236           continue;
237         devInst = childInst;
238       }
239 
240       // get the com port
241       if (devInst != 0)
242       {
243         if (GetComPortFromDevNode(devInst, deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName)))
244         {
245           snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", devId);
246           deviceList[iFound].iVendorId = (uint16_t)iVendor;
247           deviceList[iFound].iProductId = (uint16_t)iProduct;
248           deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
249           iFound++;
250         }
251       }
252     }
253 
254     free(buffer);
255   }
256 #else
257   (void)deviceList;
258   (void)iBufSize;
259   (void)strDevicePath;
260 #endif
261 
262   return iFound;
263 }
264 
FindAdaptersApple(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)265 uint8_t CUSBCECAdapterDetection::FindAdaptersApple(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
266 {
267   uint8_t iFound(0);
268 
269 #if defined(__APPLE__)
270   kern_return_t	kresult;
271   char bsdPath[MAXPATHLEN] = { 0 };
272   io_iterator_t	serialPortIterator;
273 
274   CFMutableDictionaryRef classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue);
275   if (classesToMatch)
276   {
277     CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes));
278     kresult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, &serialPortIterator);
279     if (kresult == KERN_SUCCESS)
280     {
281       io_object_t serialService;
282       while ((serialService = IOIteratorNext(serialPortIterator)))
283       {
284         int iVendor = 0, iProduct = 0;
285         CFTypeRef	bsdPathAsCFString;
286 
287         // fetch the device path.
288         bsdPathAsCFString = IORegistryEntryCreateCFProperty(serialService,
289           CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
290         if (bsdPathAsCFString)
291         {
292           // convert the path from a CFString to a C (NUL-terminated) string.
293           CFStringGetCString((CFStringRef)bsdPathAsCFString, bsdPath, MAXPATHLEN - 1, kCFStringEncodingUTF8);
294           CFRelease(bsdPathAsCFString);
295 
296           // now walk up the hierarchy until we find the entry with vendor/product IDs
297           io_registry_entry_t parent;
298           CFTypeRef vendorIdAsCFNumber = NULL;
299           CFTypeRef productIdAsCFNumber = NULL;
300           kern_return_t kresult = IORegistryEntryGetParentEntry(serialService, kIOServicePlane, &parent);
301           while (kresult == KERN_SUCCESS)
302           {
303             vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
304               kIOServicePlane, CFSTR(kUSBVendorID), kCFAllocatorDefault, 0);
305             productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent,
306               kIOServicePlane, CFSTR(kUSBProductID), kCFAllocatorDefault, 0);
307             if (vendorIdAsCFNumber && productIdAsCFNumber)
308             {
309               CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberIntType, &iVendor);
310               CFRelease(vendorIdAsCFNumber);
311               CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberIntType, &iProduct);
312               CFRelease(productIdAsCFNumber);
313               IOObjectRelease(parent);
314               break;
315             }
316             io_registry_entry_t oldparent = parent;
317             kresult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent);
318             IOObjectRelease(oldparent);
319           }
320           if (strlen(bsdPath) && iVendor == CEC_VID && (iProduct == CEC_PID || iProduct == CEC_PID2))
321           {
322             if (!strDevicePath || !strcmp(bsdPath, strDevicePath))
323             {
324               // on darwin, the device path is the same as the comm path.
325               if (iFound == 0 || strcmp(deviceList[iFound - 1].strComName, bsdPath))
326               {
327                 snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", bsdPath);
328                 snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", bsdPath);
329                 deviceList[iFound].iVendorId = iVendor;
330                 deviceList[iFound].iProductId = iProduct;
331                 deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
332                 iFound++;
333               }
334             }
335           }
336         }
337         IOObjectRelease(serialService);
338       }
339     }
340     IOObjectRelease(serialPortIterator);
341   }
342 #else
343   (void)deviceList;
344   (void)iBufSize;
345   (void)strDevicePath;
346 #endif
347   return iFound;
348 }
349 
FindAdaptersUdev(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)350 uint8_t CUSBCECAdapterDetection::FindAdaptersUdev(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
351 {
352   uint8_t iFound(0);
353 
354 #if defined(HAVE_LIBUDEV)
355   struct udev *udev;
356   if (!(udev = udev_new()))
357     return -1;
358 
359   struct udev_enumerate *enumerate;
360   struct udev_list_entry *devices, *dev_list_entry;
361   struct udev_device *dev, *pdev;
362   enumerate = udev_enumerate_new(udev);
363 
364   udev_enumerate_add_match_subsystem(enumerate, "tty");
365   udev_enumerate_scan_devices(enumerate);
366   devices = udev_enumerate_get_list_entry(enumerate);
367   udev_list_entry_foreach(dev_list_entry, devices)
368   {
369     const char *strPath;
370     strPath = udev_list_entry_get_name(dev_list_entry);
371 
372     dev = udev_device_new_from_syspath(udev, strPath);
373     if (!dev)
374       continue;
375 
376     pdev = udev_device_get_parent(udev_device_get_parent(dev));
377     if (!pdev || !udev_device_get_sysattr_value(pdev, "idVendor") || !udev_device_get_sysattr_value(pdev, "idProduct"))
378     {
379       udev_device_unref(dev);
380       continue;
381     }
382 
383     int iVendor, iProduct;
384     sscanf(udev_device_get_sysattr_value(pdev, "idVendor"), "%x", &iVendor);
385     sscanf(udev_device_get_sysattr_value(pdev, "idProduct"), "%x", &iProduct);
386     if (iVendor == CEC_VID && (iProduct == CEC_PID || iProduct == CEC_PID2))
387     {
388       std::string strPath(udev_device_get_syspath(pdev));
389       if (!strDevicePath || !strcmp(strPath.c_str(), strDevicePath))
390       {
391         std::string strComm(strPath);
392         if (FindComPort(strComm) && (iFound == 0 || strcmp(deviceList[iFound - 1].strComName, strComm.c_str())))
393         {
394           snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", strPath.c_str());
395           snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", strComm.c_str());
396           deviceList[iFound].iVendorId = iVendor;
397           deviceList[iFound].iProductId = iProduct;
398           deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
399           iFound++;
400         }
401       }
402     }
403     udev_device_unref(dev);
404 
405     if (iFound >= iBufSize)
406       break;
407   }
408 
409   udev_enumerate_unref(enumerate);
410   udev_unref(udev);
411 #else
412   (void)deviceList;
413   (void)iBufSize;
414   (void)strDevicePath;
415 #endif
416 
417   return iFound;
418 }
419 
FindAdaptersLinux(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)420 uint8_t CUSBCECAdapterDetection::FindAdaptersLinux(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
421 {
422   uint8_t iFound(0);
423 
424 #if defined(__linux__)
425   std::string strSysfsPath("/sys/bus/usb/devices");
426   DIR *dir;
427 
428   if ((dir = opendir(strSysfsPath.c_str())) != NULL)
429   {
430     struct dirent *dent;
431 
432     while ((dent = readdir(dir)) != NULL)
433     {
434       std::string strDevice = StringUtils::Format("%s/%s", strSysfsPath.c_str(), dent->d_name);
435       unsigned int iVendor, iProduct;
436 
437       if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
438         continue;
439 
440       std::ifstream fVendor(StringUtils::Format("%s/idVendor", strDevice.c_str()));
441       if (!fVendor)
442         continue;
443       fVendor >> std::hex >> iVendor;
444 
445       std::ifstream fProduct(StringUtils::Format("%s/idProduct", strDevice.c_str()));
446       if (!fProduct)
447         continue;
448       fProduct >> std::hex >> iProduct;
449 
450       if (iVendor != CEC_VID || (iProduct != CEC_PID && iProduct != CEC_PID2))
451         continue;
452 
453       if (strDevicePath && strcmp(strDevice.c_str(), strDevicePath))
454         continue;
455 
456       std::string strPort(strDevice);
457       if (FindComPort(strPort) && (iFound == 0 || strcmp(deviceList[iFound - 1].strComName, strPort.c_str())))
458       {
459         snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", strDevice.c_str());
460         snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", strPort.c_str());
461         deviceList[iFound].iVendorId = iVendor;
462         deviceList[iFound].iProductId = iProduct;
463         deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
464         iFound++;
465       }
466 
467       if (iFound >= iBufSize)
468         break;
469     }
470 
471     closedir(dir);
472   }
473 
474 #else
475   (void)deviceList;
476   (void)iBufSize;
477   (void)strDevicePath;
478 #endif
479 
480   return iFound;
481 }
482 
FindAdaptersFreeBSD(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)483 uint8_t CUSBCECAdapterDetection::FindAdaptersFreeBSD(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
484 {
485   uint8_t iFound(0);
486 
487 #if defined(__FreeBSD__) || defined(__DragonFly__)
488   char devicePath[PATH_MAX + 1];
489   char infos[512];
490   char sysctlname[32];
491   char ttyname[8];
492   char *pos;
493   size_t infos_size = sizeof(infos);
494   int i;
495 
496   for (i = 0;; ++i)
497   {
498     unsigned int iVendor, iProduct;
499     memset(infos, 0, sizeof(infos));
500     (void)snprintf(sysctlname, sizeof(sysctlname),
501       "dev.umodem.%d.%%pnpinfo", i);
502     if (sysctlbyname(sysctlname, infos, &infos_size,
503       NULL, 0) != 0)
504       break;
505     pos = strstr(infos, "vendor=");
506     if (pos == NULL)
507       continue;
508     sscanf(pos, "vendor=%x ", &iVendor);
509 
510     pos = strstr(infos, "product=");
511     if (pos == NULL)
512       continue;
513     sscanf(pos, "product=%x ", &iProduct);
514 
515     if (iVendor != CEC_VID || (iProduct != CEC_PID && iProduct != CEC_PID2))
516       continue;
517 
518     pos = strstr(infos, "ttyname=");
519     if (pos == NULL)
520       continue;
521     sscanf(pos, "ttyname=%s ", ttyname);
522 
523     (void)snprintf(devicePath, sizeof(devicePath),
524       "/dev/tty%s", ttyname);
525 
526     if (strDevicePath) {
527       char currStrDevicePath[512];
528       int port = 0;
529       int devaddr = 0;
530       memset(currStrDevicePath, 0, sizeof(currStrDevicePath));
531       memset(infos, 0, sizeof(infos));
532       (void)snprintf(sysctlname, sizeof(sysctlname),
533         "dev.umodem.%d.%%location", i);
534       if (sysctlbyname(sysctlname, infos, &infos_size,
535         NULL, 0) != 0)
536         break;
537 
538       pos = strstr(infos, "port=");
539       if (pos == NULL)
540         continue;
541       sscanf(pos, "port=%d ", &port);
542 
543       pos = strstr(infos, "devaddr=");
544       if (pos == NULL)
545         continue;
546       sscanf(pos, "devaddr=%d ", &devaddr);
547 
548       (void)snprintf(currStrDevicePath, sizeof(currStrDevicePath),
549         "/dev/ugen%d.%d", port, devaddr);
550 
551       if (strcmp(currStrDevicePath, strDevicePath) != 0)
552         continue;
553     }
554     snprintf(deviceList[iFound].strComPath, sizeof(deviceList[iFound].strComPath), "%s", devicePath);
555     snprintf(deviceList[iFound].strComName, sizeof(deviceList[iFound].strComName), "%s", devicePath);
556     deviceList[iFound].iVendorId = iVendor;
557     deviceList[iFound].iProductId = iProduct;
558     deviceList[iFound].adapterType = ADAPTERTYPE_P8_EXTERNAL; // will be overridden when not doing a "quick scan" by the actual type
559     iFound++;
560   }
561 #else
562   (void)deviceList;
563   (void)iBufSize;
564   (void)strDevicePath;
565 #endif
566 
567   return iFound;
568 }
569 
FindAdapters(cec_adapter_descriptor * deviceList,uint8_t iBufSize,const char * strDevicePath)570 uint8_t CUSBCECAdapterDetection::FindAdapters(cec_adapter_descriptor *deviceList, uint8_t iBufSize, const char *strDevicePath /* = NULL */)
571 {
572   uint8_t iFound(0);
573   iFound = FindAdaptersApple(deviceList, iBufSize, strDevicePath);
574   if (iFound == 0)
575     iFound = FindAdaptersFreeBSD(deviceList, iBufSize, strDevicePath);
576   if (iFound == 0)
577     iFound = FindAdaptersUdev(deviceList, iBufSize, strDevicePath);
578   if (iFound == 0)
579     iFound = FindAdaptersLinux(deviceList, iBufSize, strDevicePath);
580   if (iFound == 0)
581     iFound = FindAdaptersWindows(deviceList, iBufSize, strDevicePath);
582   return iFound;
583 }
584