1 /*- 2 * Copyright (c) 2017 Netflix, Inc 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 __FBSDID("$FreeBSD$"); 29 30 #include <sys/param.h> 31 #include <ctype.h> 32 #include <devinfo.h> 33 #include <err.h> 34 #include <errno.h> 35 #include <fcntl.h> 36 #include <getopt.h> 37 #include <stdio.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <unistd.h> 41 #include <sys/linker.h> 42 #include <sys/module.h> 43 #include <sys/stat.h> 44 #include <sys/sysctl.h> 45 46 /* options descriptor */ 47 static struct option longopts[] = { 48 { "all", no_argument, NULL, 'a' }, 49 { "dump", no_argument, NULL, 'd' }, 50 { "hints", required_argument, NULL, 'h' }, 51 { "nomatch", required_argument, NULL, 'p' }, 52 { "unbound", no_argument, NULL, 'u' }, 53 { "verbose", no_argument, NULL, 'v' }, 54 { NULL, 0, NULL, 0 } 55 }; 56 57 #define DEVMATCH_MAX_HITS 256 58 59 static struct match_data { 60 char *descr; 61 int priority; 62 } match_data[DEVMATCH_MAX_HITS]; 63 64 static int hit_index; 65 static int all_flag; 66 static int dump_flag; 67 static char *linker_hints; 68 static char *nomatch_str; 69 static int unbound_flag; 70 static int verbose_flag; 71 72 static void *hints; 73 static void *hints_end; 74 75 static void * 76 read_hints(const char *fn, size_t *len) 77 { 78 void *h; 79 int fd; 80 struct stat sb; 81 82 fd = open(fn, O_RDONLY); 83 if (fd < 0) { 84 if (errno == ENOENT) 85 return NULL; 86 err(1, "Can't open %s for reading", fn); 87 } 88 if (fstat(fd, &sb) != 0) 89 err(1, "Can't fstat %s\n", fn); 90 h = malloc(sb.st_size); 91 if (h == NULL) 92 err(1, "not enough space to read hints file of %ju bytes", (uintmax_t)sb.st_size); 93 if (read(fd, h, sb.st_size) != sb.st_size) 94 err(1, "Can't read in %ju bytes from %s", (uintmax_t)sb.st_size, fn); 95 close(fd); 96 *len = sb.st_size; 97 return h; 98 } 99 100 static void 101 read_linker_hints(void) 102 { 103 char fn[MAXPATHLEN]; 104 char *modpath, *p, *q; 105 size_t buflen, len; 106 107 if (linker_hints == NULL) { 108 if (sysctlbyname("kern.module_path", NULL, &buflen, NULL, 0) < 0) 109 errx(1, "Can't find kernel module path."); 110 modpath = malloc(buflen); 111 if (modpath == NULL) 112 err(1, "Can't get memory for modpath."); 113 if (sysctlbyname("kern.module_path", modpath, &buflen, NULL, 0) < 0) 114 errx(1, "Can't find kernel module path."); 115 p = modpath; 116 while ((q = strsep(&p, ";")) != NULL) { 117 snprintf(fn, sizeof(fn), "%s/linker.hints", q); 118 hints = read_hints(fn, &len); 119 if (hints == NULL) 120 continue; 121 break; 122 } 123 if (q == NULL) 124 errx(1, "Can't read linker hints file."); 125 } else { 126 hints = read_hints(linker_hints, &len); 127 if (hints == NULL) 128 err(1, "Can't open %s for reading", fn); 129 } 130 131 if (*(int *)(intptr_t)hints != LINKER_HINTS_VERSION) { 132 warnx("Linker hints version %d doesn't match expected %d.", 133 *(int *)(intptr_t)hints, LINKER_HINTS_VERSION); 134 free(hints); 135 hints = NULL; 136 } 137 if (hints != NULL) 138 hints_end = (void *)((intptr_t)hints + (intptr_t)len); 139 } 140 141 static int 142 getint(void **ptr) 143 { 144 int *p = *ptr; 145 int rv; 146 147 p = (int *)roundup2((intptr_t)p, sizeof(int)); 148 rv = *p++; 149 *ptr = p; 150 return rv; 151 } 152 153 static void 154 getstr(void **ptr, char *val) 155 { 156 int *p = *ptr; 157 char *c = (char *)p; 158 int len = *(uint8_t *)c; 159 160 memcpy(val, c + 1, len); 161 val[len] = 0; 162 c += len + 1; 163 *ptr = (void *)c; 164 } 165 166 static int 167 pnpval_as_int(const char *val, const char *pnpinfo) 168 { 169 int rv; 170 char key[256]; 171 char *cp; 172 173 if (pnpinfo == NULL) 174 return -1; 175 176 cp = strchr(val, ';'); 177 key[0] = ' '; 178 if (cp == NULL) 179 strlcpy(key + 1, val, sizeof(key) - 1); 180 else { 181 memcpy(key + 1, val, cp - val); 182 key[cp - val + 1] = '\0'; 183 } 184 strlcat(key, "=", sizeof(key)); 185 if (strncmp(key + 1, pnpinfo, strlen(key + 1)) == 0) 186 rv = strtol(pnpinfo + strlen(key + 1), NULL, 0); 187 else { 188 cp = strstr(pnpinfo, key); 189 if (cp == NULL) 190 rv = -1; 191 else 192 rv = strtol(cp + strlen(key), NULL, 0); 193 } 194 return rv; 195 } 196 197 static void 198 quoted_strcpy(char *dst, const char *src) 199 { 200 char q = ' '; 201 202 if (*src == '\'' || *src == '"') 203 q = *src++; 204 while (*src && *src != q) 205 *dst++ = *src++; // XXX backtick quoting 206 *dst++ = '\0'; 207 // XXX overflow 208 } 209 210 static char * 211 pnpval_as_str(const char *val, const char *pnpinfo) 212 { 213 static char retval[256]; 214 char key[256]; 215 char *cp; 216 217 if (pnpinfo == NULL) { 218 *retval = '\0'; 219 return retval; 220 } 221 222 cp = strchr(val, ';'); 223 key[0] = ' '; 224 if (cp == NULL) 225 strlcpy(key + 1, val, sizeof(key) - 1); 226 else { 227 memcpy(key + 1, val, cp - val); 228 key[cp - val + 1] = '\0'; 229 } 230 strlcat(key, "=", sizeof(key)); 231 if (strncmp(key + 1, pnpinfo, strlen(key + 1)) == 0) 232 quoted_strcpy(retval, pnpinfo + strlen(key + 1)); 233 else { 234 cp = strstr(pnpinfo, key); 235 if (cp == NULL) 236 strcpy(retval, "MISSING"); 237 else 238 quoted_strcpy(retval, cp + strlen(key)); 239 } 240 return retval; 241 } 242 243 static int 244 match_data_compare(const void *_pa, const void *_pb) 245 { 246 const struct match_data *pa = _pa; 247 const struct match_data *pb = _pb; 248 249 /* biggest value first */ 250 if (pa->priority > pb->priority) 251 return (-1); 252 else if (pa->priority < pb->priority) 253 return (1); 254 255 /* then sort by string */ 256 return (strcmp(pa->descr, pb->descr)); 257 } 258 259 static int 260 bitrev16(int input) 261 { 262 int retval = 0; 263 int x; 264 265 for (x = 0; x != 16; x++) { 266 if ((input >> x) & 1) 267 retval |= (0x8000 >> x); 268 } 269 return (retval); 270 } 271 272 static void 273 search_hints(const char *bus, const char *dev, const char *pnpinfo) 274 { 275 char val1[256], val2[256]; 276 int ival, len, ents, i, notme, mask, bit, v, found; 277 void *ptr, *walker; 278 char *lastmod = NULL, *cp, *s; 279 280 walker = hints; 281 getint(&walker); 282 found = 0; 283 if (verbose_flag) 284 printf("Searching bus %s dev %s for pnpinfo %s\n", 285 bus, dev, pnpinfo); 286 while (walker < hints_end) { 287 len = getint(&walker); 288 ival = getint(&walker); 289 ptr = walker; 290 switch (ival) { 291 case MDT_VERSION: 292 getstr(&ptr, val1); 293 ival = getint(&ptr); 294 getstr(&ptr, val2); 295 if (dump_flag || verbose_flag) 296 printf("Version: if %s.%d kmod %s\n", val1, ival, val2); 297 break; 298 case MDT_MODULE: 299 getstr(&ptr, val1); 300 getstr(&ptr, val2); 301 if (lastmod) 302 free(lastmod); 303 lastmod = strdup(val2); 304 if (dump_flag || verbose_flag) 305 printf("Module %s in %s\n", val1, val2); 306 break; 307 case MDT_PNP_INFO: 308 if (!dump_flag && !unbound_flag && lastmod && strcmp(lastmod, "kernel") == 0) 309 break; 310 getstr(&ptr, val1); 311 getstr(&ptr, val2); 312 ents = getint(&ptr); 313 if (dump_flag || verbose_flag) 314 printf("PNP info for bus %s format %s %d entries (%s)\n", 315 val1, val2, ents, lastmod); 316 if (strcmp(val1, "usb") == 0) { 317 if (verbose_flag) 318 printf("Treating usb as uhub -- bug in source table still?\n"); 319 strcpy(val1, "uhub"); 320 } 321 if (bus && strcmp(val1, bus) != 0) { 322 if (verbose_flag) 323 printf("Skipped because table for bus %s, looking for %s\n", 324 val1, bus); 325 break; 326 } 327 for (i = 0; i < ents; i++) { 328 if (verbose_flag) 329 printf("---------- Entry %d ----------\n", i); 330 if (dump_flag) 331 printf(" "); 332 cp = val2; 333 notme = 0; 334 mask = -1; 335 bit = -1; 336 do { 337 switch (*cp) { 338 /* All integer fields */ 339 case 'I': 340 case 'J': 341 case 'G': 342 case 'L': 343 case 'M': 344 ival = getint(&ptr); 345 if (dump_flag) { 346 printf("%#x:", ival); 347 break; 348 } 349 if (bit >= 0 && ((1 << bit) & mask) == 0) 350 break; 351 v = pnpval_as_int(cp + 2, pnpinfo); 352 if (verbose_flag) 353 printf("Matching %s (%c) table=%#x tomatch=%#x\n", 354 cp + 2, *cp, v, ival); 355 switch (*cp) { 356 case 'J': 357 if (ival == -1) 358 break; 359 /*FALLTHROUGH*/ 360 case 'I': 361 if (v != ival) 362 notme++; 363 break; 364 case 'G': 365 if (v < ival) 366 notme++; 367 break; 368 case 'L': 369 if (v > ival) 370 notme++; 371 break; 372 case 'M': 373 mask = ival; 374 break; 375 } 376 break; 377 /* String fields */ 378 case 'D': 379 case 'Z': 380 getstr(&ptr, val1); 381 if (dump_flag) { 382 printf("'%s':", val1); 383 break; 384 } 385 if (*cp == 'D') 386 break; 387 s = pnpval_as_str(cp + 2, pnpinfo); 388 if (strcmp(s, val1) != 0) 389 notme++; 390 break; 391 /* Key override fields, required to be last in the string */ 392 case 'T': 393 /* 394 * This is imperfect and only does one key and will be redone 395 * to be more general for multiple keys. Currently, nothing 396 * does that. 397 */ 398 if (dump_flag) /* No per-row data stored */ 399 break; 400 if (cp[strlen(cp) - 1] == ';') /* Skip required ; at end */ 401 cp[strlen(cp) - 1] = '\0'; /* in case it's not there */ 402 if ((s = strstr(pnpinfo, cp + 2)) == NULL) 403 notme++; 404 else if (s > pnpinfo && s[-1] != ' ') 405 notme++; 406 break; 407 default: 408 fprintf(stderr, "Unknown field type %c\n:", *cp); 409 break; 410 } 411 bit++; 412 cp = strchr(cp, ';'); 413 if (cp) 414 cp++; 415 } while (cp && *cp); 416 if (dump_flag) 417 printf("\n"); 418 else if (!notme) { 419 if (!unbound_flag) { 420 char *descr = NULL; 421 422 if (all_flag) 423 asprintf(&descr, "%s: %s", *dev ? dev : "unattached", lastmod); 424 else 425 asprintf(&descr, "%s", lastmod); 426 if (verbose_flag) 427 printf("Matches --- %s ---\n", lastmod); 428 429 if (descr != NULL && hit_index < DEVMATCH_MAX_HITS) { 430 match_data[hit_index].descr = descr; 431 match_data[hit_index].priority = bitrev16(mask); 432 hit_index++; 433 } else { 434 free(descr); 435 } 436 } 437 found++; 438 } 439 } 440 break; 441 default: 442 if (dump_flag) 443 printf("Unknown Type %d len %d\n", ival, len); 444 break; 445 } 446 walker = (void *)(len - sizeof(int) + (intptr_t)walker); 447 } 448 if (hit_index != 0) { 449 /* sort hits by priority */ 450 mergesort(match_data, hit_index, sizeof(match_data[0]), &match_data_compare); 451 452 /* printout */ 453 for (i = 0; i != hit_index; i++) { 454 puts(match_data[i].descr); 455 free(match_data[i].descr); 456 } 457 458 /* reset hit_index */ 459 hit_index = 0; 460 } 461 if (unbound_flag && found == 0 && *pnpinfo) { 462 if (verbose_flag) 463 printf("------------------------- "); 464 printf("%s on %s pnpinfo %s", *dev ? dev : "unattached", bus, pnpinfo); 465 if (verbose_flag) 466 printf(" -------------------------"); 467 printf("\n"); 468 } 469 free(lastmod); 470 } 471 472 static int 473 find_unmatched(struct devinfo_dev *dev, void *arg) 474 { 475 struct devinfo_dev *parent; 476 char *bus, *p; 477 478 do { 479 if (!all_flag && dev->dd_name[0] != '\0') 480 break; 481 if (!(dev->dd_flags & DF_ENABLED)) 482 break; 483 parent = devinfo_handle_to_device(dev->dd_parent); 484 bus = strdup(parent->dd_name); 485 p = bus + strlen(bus) - 1; 486 while (p >= bus && isdigit(*p)) 487 p--; 488 *++p = '\0'; 489 if (verbose_flag) 490 printf("Searching %s %s bus at %s for pnpinfo %s\n", 491 dev->dd_name, bus, dev->dd_location, dev->dd_pnpinfo); 492 search_hints(bus, dev->dd_name, dev->dd_pnpinfo); 493 free(bus); 494 } while (0); 495 496 return (devinfo_foreach_device_child(dev, find_unmatched, arg)); 497 } 498 499 static void 500 find_nomatch(char *nomatch) 501 { 502 char *bus, *pnpinfo, *tmp; 503 504 /* 505 * Find our bus name. It will include the unit number. We have to search 506 * backwards to avoid false positive for any PNP string that has ' on ' 507 * in them, which would come earlier in the string. Like if there were 508 * an 'Old Bard' ethernet card made by 'Stratford on Avon Hardware' or 509 * something silly like that. 510 */ 511 tmp = nomatch + strlen(nomatch) - 4; 512 while (tmp > nomatch && strncmp(tmp, " on ", 4) != 0) 513 tmp--; 514 if (tmp == nomatch) 515 errx(1, "No bus found in nomatch string: '%s'", nomatch); 516 bus = tmp + 4; 517 *tmp = '\0'; 518 tmp = bus + strlen(bus) - 1; 519 while (tmp > bus && isdigit(*tmp)) 520 tmp--; 521 *++tmp = '\0'; 522 523 /* 524 * Note: the NOMATCH events place both the bus location as well as the 525 * pnp info after the 'at' and we don't know where one stops and the 526 * other begins, so we pass the whole thing to our search routine. 527 */ 528 if (*nomatch == '?') 529 nomatch++; 530 if (strncmp(nomatch, " at ", 4) != 0) 531 errx(1, "Malformed NOMATCH string: '%s'", nomatch); 532 pnpinfo = nomatch + 4; 533 534 search_hints(bus, "", pnpinfo); 535 536 exit(0); 537 } 538 539 static void 540 usage(void) 541 { 542 543 errx(1, "devmatch [-adv] [-p nomatch] [-h linker-hints]"); 544 } 545 546 int 547 main(int argc, char **argv) 548 { 549 struct devinfo_dev *root; 550 int ch; 551 552 while ((ch = getopt_long(argc, argv, "adh:p:uv", 553 longopts, NULL)) != -1) { 554 switch (ch) { 555 case 'a': 556 all_flag++; 557 break; 558 case 'd': 559 dump_flag++; 560 break; 561 case 'h': 562 linker_hints = optarg; 563 break; 564 case 'p': 565 nomatch_str = optarg; 566 break; 567 case 'u': 568 unbound_flag++; 569 break; 570 case 'v': 571 verbose_flag++; 572 break; 573 default: 574 usage(); 575 } 576 } 577 argc -= optind; 578 argv += optind; 579 580 if (argc >= 1) 581 usage(); 582 583 read_linker_hints(); 584 if (dump_flag) { 585 search_hints(NULL, NULL, NULL); 586 exit(0); 587 } 588 589 if (nomatch_str != NULL) 590 find_nomatch(nomatch_str); 591 if (devinfo_init()) 592 err(1, "devinfo_init"); 593 if ((root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL) 594 errx(1, "can't find root device"); 595 devinfo_foreach_device_child(root, find_unmatched, (void *)0); 596 devinfo_free(); 597 } 598