1
0

aoe-sancheck.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. /* Copyright Coraid, Inc. 2010. All Rights Reserved. */
  2. #include <stdio.h>
  3. #include <sched.h>
  4. #include <stdlib.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. #include <string.h>
  8. #include <time.h>
  9. #include <stdlib.h>
  10. #include <sys/ioctl.h>
  11. #include <pthread.h>
  12. #include <poll.h>
  13. #include <errno.h>
  14. #include <netpacket/packet.h>
  15. #include <net/ethernet.h>
  16. #include <sys/types.h>
  17. #include <net/if.h>
  18. #include <netinet/in.h>
  19. #include <arpa/inet.h>
  20. #include <sys/socket.h>
  21. #define nelem(x) (sizeof(x)/sizeof((x)[0]))
  22. #define nil NULL
  23. #define vprintf(...) if (qflag) ; else fprintf(stderr, __VA_ARGS__)
  24. #define mintu(x) ((x) > 60 ? x : 60)
  25. typedef unsigned char uchar;
  26. typedef unsigned long ulong;
  27. typedef struct Aoe Aoe;
  28. typedef struct Qc Qc;
  29. typedef struct Ata Ata;
  30. typedef struct Lun Lun;
  31. typedef struct Eth Eth;
  32. typedef struct Targ Targ;
  33. typedef struct Mac Mac;
  34. struct Aoe {
  35. uchar dst[6];
  36. uchar src[6];
  37. uchar type[2];
  38. uchar flags;
  39. uchar error;
  40. uchar major[2];
  41. uchar minor;
  42. uchar cmd;
  43. uchar tag[4];
  44. };
  45. struct Qc {
  46. Aoe h;
  47. uchar bufcnt[2];
  48. uchar firmware[2];
  49. uchar scnt;
  50. uchar vercmd;
  51. uchar len[2];
  52. // uchar data[1024];
  53. };
  54. struct Ata {
  55. Aoe h;
  56. uchar aflag;
  57. uchar err;
  58. uchar scnt;
  59. uchar cmd;
  60. uchar lba[6];
  61. uchar res[2];
  62. };
  63. struct Lun {
  64. Lun *next;
  65. int state;
  66. char ea[6];
  67. int major;
  68. int minor;
  69. int nsect;
  70. int maxsect;
  71. int id;
  72. };
  73. struct Eth {
  74. Lun *luns;
  75. int fd;
  76. char *name;
  77. char ea[6];
  78. int mtu;
  79. int up;
  80. uchar pkt[16*1024];
  81. };
  82. struct Targ {
  83. Targ *next;
  84. int major;
  85. int minor;
  86. };
  87. struct Mac {
  88. Mac *next;
  89. char ea[6];
  90. };
  91. enum {
  92. Neth= 16,
  93. Nstack= 16*1024,
  94. Nws= 5,
  95. Lnew= 0,
  96. Lprobe,
  97. Arsp= (1<<3),
  98. Aerr= (1<<2),
  99. Cata= 0,
  100. Cqc= 1,
  101. ETaoe= 0x88a2,
  102. };
  103. int ethlist(char **, int);
  104. int ethopen(Eth *);
  105. void *jcheck(void *);
  106. int jinput(Eth *);
  107. Lun *findlun(Eth *e, Aoe *a);
  108. void jprobe(Eth *e, Lun *lun);
  109. void printlist(void);
  110. void printsancheck(void);
  111. ulong nhgetl(uchar *);
  112. ushort nhgets(uchar *);
  113. void hnputs(uchar *, ushort);
  114. void hnputl(uchar *, ulong);
  115. void *mallocz(int);
  116. void inserttarg(int, int);
  117. void insertmac(Mac **, char *);
  118. void sancheck(int, int);
  119. void ifsummary(void);
  120. int ifup(char *);
  121. void inserteth(char **, int, char *);
  122. char *getpciid(char *, int, char *);
  123. Eth eth[Neth];
  124. int waitsecs = Nws;
  125. pthread_t threads[Neth];
  126. Targ *targlist;
  127. int
  128. main(int argc, char **argv)
  129. {
  130. int n, i;
  131. char *ethnames[Neth];
  132. Eth *e;
  133. memset(ethnames, 0, sizeof ethnames);
  134. printf("Probing...");
  135. fflush(0);
  136. n = ethlist(ethnames, nelem(ethnames));
  137. for (i=0; i<n; i++) {
  138. e = &eth[i];
  139. e->name = ethnames[i];
  140. e->up = ifup(ethnames[i]);
  141. if (pthread_create(&threads[i], 0, jcheck, e)) {
  142. fprintf(stderr, "pthread_create failed.\n");
  143. break;
  144. }
  145. }
  146. n = i;
  147. for (i=0; i<n; i++)
  148. pthread_join(threads[i], 0);
  149. printf("done.\n");
  150. printsancheck();
  151. if (argc == 2 && strcmp(argv[1], "-v") == 0)
  152. printlist();
  153. return 0;
  154. }
  155. char *
  156. cea(char *op, char *ea)
  157. {
  158. int i;
  159. char *hex = "0123456789abcdef", *p;
  160. p = op;
  161. for (i=0; i<6; i++) {
  162. *p++ = hex[((ea[i] >> 4) & 0xf)];
  163. *p++ = hex[ea[i] & 0xf];
  164. }
  165. *p = 0;
  166. return op;
  167. }
  168. void
  169. printlist(void)
  170. {
  171. Eth *e;
  172. Lun *lun;
  173. char mac[13];
  174. for (e=eth; e->name; e++) {
  175. printf("%s:\n", e->name);
  176. for (lun=e->luns; lun; lun=lun->next)
  177. printf("e%d.%d %s %d\n", lun->major, lun->minor, cea(mac, lun->ea), lun->maxsect);
  178. printf("\n");
  179. }
  180. }
  181. void
  182. timewait(int secs) /* arrange for a sig_alarm signal after `secs' seconds */
  183. {
  184. struct sigaction sa;
  185. void catch(int);
  186. memset(&sa, 0, sizeof sa);
  187. sa.sa_handler = catch;
  188. sa.sa_flags = SA_RESETHAND;
  189. sigaction(SIGALRM, &sa, NULL);
  190. alarm(secs);
  191. }
  192. int
  193. discover(Eth *e)
  194. {
  195. Aoe *a;
  196. Qc *q;
  197. memset(e->pkt, 0, sizeof e->pkt);
  198. a = (Aoe *) e->pkt;
  199. memset(a->dst, 0xff, 6);
  200. memmove(a->src, e->ea, 6);
  201. hnputs(a->type, ETaoe);
  202. hnputl(a->tag, 1<<31);
  203. hnputs(a->major, 0xffff);
  204. a->minor = 0xff;
  205. a->cmd = Cqc;
  206. a->flags = 0x10;
  207. if (write(e->fd, a, mintu(sizeof *q)) <= 0)
  208. return -1;
  209. return 0;
  210. }
  211. void *
  212. jcheck(void *v)
  213. {
  214. Eth *e = v;
  215. int n;
  216. time_t t, nt;
  217. struct pollfd pd;
  218. if (ethopen(e) < 0)
  219. return 0;
  220. if (discover(e) < 0) {
  221. fprintf(stderr, "skipping %s, discover failure: %s\n", e->name, strerror(errno));
  222. return 0;
  223. }
  224. pd.fd = e->fd;
  225. pd.events = POLLIN;
  226. t = time(0);
  227. for (;;) {
  228. nt = time(0);
  229. if (nt-t >= waitsecs)
  230. return 0;
  231. if (poll(&pd, 1, waitsecs*1000) > 0)
  232. if ((n = read(e->fd, e->pkt, sizeof e->pkt)) > 0)
  233. if (jinput(e))
  234. t = time(0);
  235. }
  236. }
  237. /* return 1 == useful, 0 == not useful */
  238. int
  239. jinput(Eth *e)
  240. {
  241. Aoe *a;
  242. Qc *q;
  243. Ata *aa;
  244. Lun *lun;
  245. ulong tag;
  246. int n;
  247. a = (Aoe *) e->pkt;
  248. if ((a->flags & (Arsp|Aerr)) != Arsp)
  249. return 0;
  250. if ((a->tag[0] & 0x80) == 0)
  251. return 0;
  252. tag = nhgetl(a->tag);
  253. switch (a->cmd) {
  254. case Cqc:
  255. q = (Qc *) a;
  256. lun = findlun(e, a);
  257. if (lun->state == Lnew) {
  258. lun->nsect = q->scnt;
  259. jprobe(e, lun);
  260. lun->state = Lprobe;
  261. break;
  262. }
  263. return 0;
  264. case Cata:
  265. aa = (Ata *) a;
  266. lun = findlun(e, a);
  267. if (lun == nil)
  268. return 0;
  269. if (lun->id != tag>>16) {
  270. printf("lun->id %d != tag %ld for %d.%d\n", lun->id, tag>>16, lun->major, lun->minor);
  271. return 0;
  272. }
  273. n = tag & 0xff;
  274. if (n > lun->maxsect)
  275. lun->maxsect = n;
  276. break;
  277. default:
  278. return 0;
  279. }
  280. return 1;
  281. }
  282. void
  283. hnputl(uchar *p, ulong n)
  284. {
  285. *p++ = n >> 24;
  286. *p++ = n >> 16;
  287. *p++ = n >> 8;
  288. *p = n;
  289. }
  290. void
  291. hnputs(uchar *p, ushort s)
  292. {
  293. *p++ = s >> 8;
  294. *p = s;
  295. }
  296. ushort
  297. nhgets(uchar *p)
  298. {
  299. ushort s;
  300. s = *p++;
  301. s <<= 8;
  302. s += *p++ & 0xff;
  303. return s;
  304. }
  305. ulong
  306. nhgetl(uchar *p)
  307. {
  308. ulong n;
  309. n = *p++;
  310. n <<= 8;
  311. n += *p++ & 0xff;
  312. n <<= 8;
  313. n += *p++ & 0xff;
  314. n <<= 8;
  315. n += *p++ & 0xff;
  316. return n;
  317. }
  318. void
  319. jprobe(Eth *e, Lun *lun)
  320. {
  321. Aoe *a;
  322. Ata *aa;
  323. int n;
  324. memset(e->pkt, 0, sizeof e->pkt);
  325. a = (Aoe *) e->pkt;
  326. aa = (Ata *) a;
  327. memcpy(a->dst, lun->ea, 6);
  328. memcpy(a->src, e->ea, 6);
  329. hnputs(a->type, ETaoe);
  330. hnputs(a->major, lun->major);
  331. a->minor = lun->minor;
  332. a->flags = 0x10;
  333. hnputl(a->tag, lun->id<<16);
  334. aa->cmd = 0xec;
  335. aa->scnt = 1;
  336. n = e->mtu - sizeof *aa;
  337. for (n &= ~511; n > 0; n -= 512) {
  338. a->tag[3] = n/512;
  339. if (write(e->fd, a, sizeof *aa + n) <= 0) {
  340. printf("write failed\n");
  341. }
  342. usleep(100);
  343. }
  344. }
  345. Lun *
  346. findlun(Eth *e, Aoe *a)
  347. {
  348. Lun *p, **pp;
  349. int maj, n;
  350. static int id;
  351. maj = nhgets(a->major);
  352. pp = &e->luns;
  353. for (; (p=*pp); pp=&p->next) {
  354. if (maj < p->major)
  355. continue;
  356. if (maj > p->major)
  357. break;
  358. if (a->minor < p->minor)
  359. continue;
  360. if (a->minor > p->minor)
  361. break;
  362. n = memcmp(p->ea, a->src, 6);
  363. if (n < 0)
  364. continue;
  365. if (n > 0)
  366. break;
  367. return p;
  368. }
  369. if (a->cmd == Cata)
  370. return nil;
  371. p = mallocz(sizeof *p);
  372. p->major = maj;
  373. p->minor = a->minor;
  374. memmove(p->ea, a->src, 6);
  375. p->next = *pp;
  376. p->id = 0x8000 | id++;
  377. inserttarg(p->major, p->minor);
  378. return *pp = p;
  379. }
  380. void
  381. catch(int sig)
  382. {
  383. }
  384. int
  385. getindx(int sfd, char *name) // return the index of device 'name'
  386. {
  387. struct ifreq xx;
  388. int n;
  389. strcpy(xx.ifr_name, name);
  390. n = ioctl(sfd, SIOCGIFINDEX, &xx);
  391. if (n == -1)
  392. return -1;
  393. return xx.ifr_ifindex;
  394. }
  395. int
  396. getmtu(Eth *e)
  397. {
  398. struct ifreq xx;
  399. int n;
  400. strcpy(xx.ifr_name, e->name);
  401. n = ioctl(e->fd, SIOCGIFMTU, &xx);
  402. if (n == -1) {
  403. perror("Can't get mtu");
  404. return 1500;
  405. }
  406. return xx.ifr_mtu;
  407. }
  408. int
  409. ethopen(Eth *e) // get us a raw connection to an interface
  410. {
  411. int n, sfd, rbsz, sbsz;
  412. struct sockaddr_ll sa;
  413. struct ifreq xx;
  414. rbsz = 64*1024*1024;
  415. sbsz = 64*1024*1024;
  416. memset(&sa, 0, sizeof sa);
  417. memset(&xx, 0, sizeof xx);
  418. sfd = socket(PF_PACKET, SOCK_RAW, htons(ETaoe));
  419. if (sfd == -1) {
  420. perror("got bad socket");
  421. return -1;
  422. }
  423. if (setsockopt(sfd, SOL_SOCKET, SO_RCVBUFFORCE, &rbsz, sizeof rbsz) < 0)
  424. fprintf(stderr, "Failed to set socket rcvbuf size\n");
  425. if (setsockopt(sfd, SOL_SOCKET, SO_SNDBUFFORCE, &sbsz, sizeof sbsz) < 0)
  426. fprintf(stderr, "Failed to set socket sndbuf size\n");
  427. n = getindx(sfd, e->name);
  428. sa.sll_family = AF_PACKET;
  429. sa.sll_protocol = htons(ETaoe);
  430. sa.sll_ifindex = n;
  431. n = bind(sfd, (struct sockaddr *)&sa, sizeof sa);
  432. if (n == -1) {
  433. perror("bind funky");
  434. return -1;
  435. }
  436. strcpy(xx.ifr_name, e->name);
  437. n = ioctl(sfd, SIOCGIFHWADDR, &xx);
  438. if (n == -1) {
  439. perror("Can't get hw addr");
  440. return -1;
  441. }
  442. memmove(e->ea, xx.ifr_hwaddr.sa_data, 6);
  443. e->fd = sfd;
  444. e->mtu = getmtu(e);
  445. return 0;
  446. }
  447. int
  448. ethlist(char **ifs, int nifs)
  449. {
  450. int i, s, n;
  451. struct ifreq ifr;
  452. s = socket(AF_INET, SOCK_STREAM, 0);
  453. if (s < 0)
  454. return 0;
  455. n = 0;
  456. for (i=0; i<nifs; i++) {
  457. memset(&ifr, 0, sizeof ifr);
  458. ifr.ifr_ifindex = i;
  459. if (ioctl(s, SIOCGIFNAME, &ifr) < 0)
  460. continue;
  461. if (strncmp(ifr.ifr_name, "eth", 3))
  462. continue;
  463. inserteth(ifs, nifs, ifr.ifr_name);
  464. n++;
  465. }
  466. close(s);
  467. return n;
  468. }
  469. void *
  470. mallocz(int sz)
  471. {
  472. void *p;
  473. p = malloc(sz);
  474. memset(p, 0, sz);
  475. return p;
  476. }
  477. void
  478. inserttarg(int maj, int min)
  479. {
  480. Targ *nt, *t, *p;
  481. nt = mallocz(sizeof *nt);
  482. nt->major = maj;
  483. nt->minor = min;
  484. if (targlist == NULL) {
  485. targlist = nt;
  486. return;
  487. }
  488. if (nt->major < targlist->major) {
  489. nt->next = targlist;
  490. targlist = nt;
  491. return;
  492. } else if (nt->major == targlist->major && nt->minor < targlist->minor) {
  493. nt->next = targlist;
  494. targlist = nt;
  495. return;
  496. } else if (nt->major == targlist->major && nt->minor == targlist->minor)
  497. return;
  498. for (p = targlist,t = targlist->next; t; p = t,t = t->next) {
  499. if (nt->major == t->major && nt->minor == t->minor)
  500. return;
  501. if (nt->major < t->major) {
  502. p->next = nt;
  503. nt->next = t;
  504. return;
  505. } else if (nt->major == t->major && nt->minor < t->minor) {
  506. p->next = nt;
  507. nt->next = t;
  508. return;
  509. }
  510. }
  511. p->next = nt;
  512. }
  513. void
  514. printsancheck()
  515. {
  516. Targ *t;
  517. printf("==========================================\n");
  518. printf("INTERFACE SUMMARY\n");
  519. printf("==========================================\n");
  520. printf("Name\tStatus\tMTU\tPCI ID\n");
  521. ifsummary();
  522. printf("==========================================\n");
  523. printf("DEVICE SUMMARY\n");
  524. printf("==========================================\n");
  525. printf("Device\tMacs\tPayload\tLocal Interfaces\n");
  526. for (t = targlist; t; t = t->next) {
  527. sancheck(t->major, t->minor);
  528. }
  529. }
  530. void
  531. ifsummary()
  532. {
  533. Eth *e;
  534. char buf[32];
  535. char *p;
  536. for (e=eth; e->name; e++) {
  537. p = getpciid(buf, sizeof buf, e->name);
  538. printf("%s\t%s\t%d\t%s\n", e->name, (e->up ? "UP" : "DN"), e->mtu, (p ? p : ""));
  539. }
  540. }
  541. void
  542. sancheck(int maj, int min)
  543. {
  544. Eth *e;
  545. Lun *l;
  546. Mac *ml, *m;
  547. int a, found;
  548. int ps, nsect, nea, nloc;
  549. int mtu, mtuflag;
  550. char buf[128];
  551. char mac[13];
  552. nsect = mtu = 0;
  553. mtuflag = 0;
  554. nloc = nea = 0;
  555. a = 0;
  556. ps = 0;
  557. memset(buf, 0, sizeof buf);
  558. ml = NULL;
  559. for (e=eth; e->name; e++) {
  560. found = 0;
  561. for (l = e->luns; l; l = l->next) {
  562. if (!(l->major == maj && l->minor == min))
  563. continue;
  564. found = 1;
  565. if (mtu == 0)
  566. mtu = e->mtu;
  567. else if (mtu != e->mtu) {
  568. mtuflag = 1;
  569. mtu = (e->mtu > mtu ? e->mtu : mtu);
  570. }
  571. insertmac(&ml, l->ea);
  572. if (ps == 0)
  573. ps = l->maxsect;
  574. else
  575. ps = (l->maxsect < ps ? l->maxsect : ps);
  576. nsect = l->nsect;
  577. }
  578. if (found) {
  579. snprintf(buf + a,(sizeof buf) - a, "%s%s", (a ? ",": ""), e->name);
  580. a += strlen(e->name) + (a ? 1 : 0);
  581. nloc++;
  582. }
  583. }
  584. for (m = ml; m; ml = m, nea++) {
  585. m = m->next;
  586. free(ml);
  587. }
  588. printf("e%d.%d\t%4d\t%d\t%s\n", maj, min, nea, nsect*512, buf);
  589. if (nea != nloc)
  590. printf(" Mismatched number of local vs remote interfaces\n");
  591. for (e=eth; e->name; e++) {
  592. found = 0;
  593. for (l = e->luns; l; l = l->next)
  594. if (l->major == maj && l->minor == min) {
  595. found = 1;
  596. if (l->maxsect < (e->mtu-32)/512)
  597. printf(" The path %s->%s is only capable of %d byte payloads\n", e->name, cea(mac, l->ea), l->maxsect*512);
  598. }
  599. if (found) {
  600. if (e->mtu < nsect*512 + 36)
  601. printf(" %s: MTU (%d) not set optimally for device's capable payload\n",e->name, e->mtu);
  602. if (e->mtu < mtu)
  603. printf(" %s: MTU different from other interfaces (%d vs. %d)\n", e->name, e->mtu, mtu);
  604. }
  605. }
  606. }
  607. void
  608. insertmac(Mac **ml, char *m)
  609. {
  610. Mac *nm,*p, *pp;
  611. for (p = *ml, pp=NULL; p; pp = p, p = p->next)
  612. if (memcmp(p->ea, m, 6) == 0)
  613. return;
  614. nm = mallocz(sizeof *nm);
  615. memcpy(nm->ea, m, 6);
  616. if (pp)
  617. pp->next = nm;
  618. else
  619. *ml = nm;
  620. }
  621. int
  622. ifup(char *ethname)
  623. {
  624. struct ifreq ifr;
  625. int r, s;
  626. memset(&ifr, 0, sizeof ifr);
  627. s = socket(AF_INET, SOCK_STREAM, 0);
  628. if (s < 0)
  629. return 0;
  630. strcpy(ifr.ifr_name, ethname);
  631. if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) {
  632. close(s);
  633. return 0;
  634. }
  635. r = ifr.ifr_flags & IFF_UP;
  636. close(s);
  637. return r;
  638. }
  639. void
  640. inserteth(char **ifs, int nifs, char *n)
  641. {
  642. int i, j;
  643. char a[64], b[64];
  644. memset(a, 0, sizeof a);
  645. memset(b, 0, sizeof b);
  646. for (i=0; i < nifs; i++) {
  647. if (ifs[i] == 0) {
  648. ifs[i] = strdup(n);
  649. break;
  650. }
  651. j = strcmp(n, ifs[i]);
  652. if (j < 0) {
  653. strcpy(a, n);
  654. for (; ifs[i]; i++) {
  655. strcpy(b, ifs[i]);
  656. free(ifs[i]);
  657. ifs[i] = strdup(a);
  658. strcpy(a, b);
  659. }
  660. ifs[i] = strdup(a);
  661. break;
  662. } else if (j == 0)
  663. break;
  664. else if (j > 0)
  665. continue;
  666. }
  667. }
  668. char *
  669. getpciid(char *b, int blen, char *n)
  670. {
  671. FILE *fd;
  672. char dev[8];
  673. char ven[8];
  674. char path[128];
  675. memset(dev, 0, sizeof dev);
  676. memset(ven, 0, sizeof ven);
  677. memset(path, 0, sizeof path);
  678. sprintf(path, "/sys/class/net/%s/device/vendor", n);
  679. if ((fd = fopen(path, "r")) == NULL)
  680. return NULL;
  681. fseek(fd, 2, SEEK_SET);
  682. if (fread(ven, 1, 4, fd) <= 0) {
  683. fclose(fd);
  684. return NULL;
  685. }
  686. fclose(fd);
  687. sprintf(path, "/sys/class/net/%s/device/device", n);
  688. if ((fd = fopen(path, "r")) == NULL)
  689. return NULL;
  690. fseek(fd, 2, SEEK_SET);
  691. if (fread(dev, 1, 4, fd) <= 0) {
  692. fclose(fd);
  693. return NULL;
  694. }
  695. fclose(fd);
  696. snprintf(b, blen, "%s:%s", ven, dev);
  697. return b;
  698. }