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 warnx("Can't read linker hints file."); 125 free(hints); 126 hints = NULL; 127 return; 128 } 129 } else { 130 hints = read_hints(linker_hints, &len); 131 if (hints == NULL) 132 err(1, "Can't open %s for reading", fn); 133 } 134 135 if (*(int *)(intptr_t)hints != LINKER_HINTS_VERSION) { 136 warnx("Linker hints version %d doesn't match expected %d.", 137 *(int *)(intptr_t)hints, LINKER_HINTS_VERSION); 138 free(hints); 139 hints = NULL; 140 } 141 if (hints != NULL) 142 hints_end = (void *)((intptr_t)hints + (intptr_t)len); 143 } 144 145 static int 146 getint(void **ptr) 147 { 148 int *p = *ptr; 149 int rv; 150 151 p = (int *)roundup2((intptr_t)p, sizeof(int)); 152 rv = *p++; 153 *ptr = p; 154 return rv; 155 } 156 157 static void 158 getstr(void **ptr, char *val) 159 { 160 int *p = *ptr; 161 char *c = (char *)p; 162 int len = *(uint8_t *)c; 163 164 memcpy(val, c + 1, len); 165 val[len] = 0; 166 c += len + 1; 167 *ptr = (void *)c; 168 } 169 170 static int 171 pnpval_as_int(const char *val, const char *pnpinfo) 172 { 173 int rv; 174 char key[256]; 175 char *cp; 176 177 if (pnpinfo == NULL) 178 return -1; 179 180 cp = strchr(val, ';'); 181 key[0] = ' '; 182 if (cp == NULL) 183 strlcpy(key + 1, val, sizeof(key) - 1); 184 else { 185 memcpy(key + 1, val, cp - val); 186 key[cp - val + 1] = '\0'; 187 } 188 strlcat(key, "=", sizeof(key)); 189 if (strncmp(key + 1, pnpinfo, strlen(key + 1)) == 0) 190 rv = strtol(pnpinfo + strlen(key + 1), NULL, 0); 191 else { 192 cp = strstr(pnpinfo, key); 193 if (cp == NULL) 194 rv = -1; 195 else 196 rv = strtol(cp + strlen(key), NULL, 0); 197 } 198 return rv; 199 } 200 201 static void 202 quoted_strcpy(char *dst, const char *src) 203 { 204 char q = ' '; 205 206 if (*src == '\'' || *src == '"') 207 q = *src++; 208 while (*src && *src != q) 209 *dst++ = *src++; // XXX backtick quoting 210 *dst++ = '\0'; 211 // XXX overflow 212 } 213 214 static char * 215 pnpval_as_str(const char *val, const char *pnpinfo) 216 { 217 static char retval[256]; 218 char key[256]; 219 char *cp; 220 221 if (pnpinfo == NULL) { 222 *retval = '\0'; 223 return retval; 224 } 225 226 cp = strchr(val, ';'); 227 key[0] = ' '; 228 if (cp == NULL) 229 strlcpy(key + 1, val, sizeof(key) - 1); 230 else { 231 memcpy(key + 1, val, cp - val); 232 key[cp - val + 1] = '\0'; 233 } 234 strlcat(key, "=", sizeof(key)); 235 if (strncmp(key + 1, pnpinfo, strlen(key + 1)) == 0) 236 quoted_strcpy(retval, pnpinfo + strlen(key + 1)); 237 else { 238 cp = strstr(pnpinfo, key); 239 if (cp == NULL) 240 strcpy(retval, "MISSING"); 241 else 242 quoted_strcpy(retval, cp + strlen(key)); 243 } 244 return retval; 245 } 246 247 static int 248 match_data_compare(const void *_pa, const void *_pb) 249 { 250 const struct match_data *pa = _pa; 251 const struct match_data *pb = _pb; 252 253 /* biggest value first */ 254 if (pa->priority > pb->priority) 255 return (-1); 256 else if (pa->priority < pb->priority) 257 return (1); 258 259 /* then sort by string */ 260 return (strcmp(pa->descr, pb->descr)); 261 } 262 263 static int 264 bitrev16(int input) 265 { 266 int retval = 0; 267 int x; 268 269 for (x = 0; x != 16; x++) { 270 if ((input >> x) & 1) 271 retval |= (0x8000 >> x); 272 } 273 return (retval); 274 } 275 276 static void 277 search_hints(const char *bus, const char *dev, const char *pnpinfo) 278 { 279 char val1[256], val2[256]; 280 int ival, len, ents, i, notme, mask, bit, v, found; 281 void *ptr, *walker; 282 char *lastmod = NULL, *cp, *s; 283 284 walker = hints; 285 getint(&walker); 286 found = 0; 287 if (verbose_flag) 288 printf("Searching bus %s dev %s for pnpinfo %s\n", 289 bus, dev, pnpinfo); 290 while (walker < hints_end) { 291 len = getint(&walker); 292 ival = getint(&walker); 293 ptr = walker; 294 switch (ival) { 295 case MDT_VERSION: 296 getstr(&ptr, val1); 297 ival = getint(&ptr); 298 getstr(&ptr, val2); 299 if (dump_flag || verbose_flag) 300 printf("Version: if %s.%d kmod %s\n", val1, ival, val2); 301 break; 302 case MDT_MODULE: 303 getstr(&ptr, val1); 304 getstr(&ptr, val2); 305 if (lastmod) 306 free(lastmod); 307 lastmod = strdup(val2); 308 if (dump_flag || verbose_flag) 309 printf("Module %s in %s\n", val1, val2); 310 break; 311 case MDT_PNP_INFO: 312 if (!dump_flag && !unbound_flag && lastmod && strcmp(lastmod, "kernel") == 0) 313 break; 314 getstr(&ptr, val1); 315 getstr(&ptr, val2); 316 ents = getint(&ptr); 317 if (dump_flag || verbose_flag) 318 printf("PNP info for bus %s format %s %d entries (%s)\n", 319 val1, val2, ents, lastmod); 320 if (strcmp(val1, "usb") == 0) { 321 if (verbose_flag) 322 printf("Treating usb as uhub -- bug in source table still?\n"); 323 strcpy(val1, "uhub"); 324 } 325 if (bus && strcmp(val1, bus) != 0) { 326 if (verbose_flag) 327 printf("Skipped because table for bus %s, looking for %s\n", 328 val1, bus); 329 break; 330 } 331 for (i = 0; i < ents; i++) { 332 if (verbose_flag) 333 printf("---------- Entry %d ----------\n", i); 334 if (dump_flag) 335 printf(" "); 336 cp = val2; 337 notme = 0; 338 mask = -1; 339 bit = -1; 340 do { 341 switch (*cp) { 342 /* All integer fields */ 343 case 'I': 344 case 'J': 345 case 'G': 346 case 'L': 347 case 'M': 348 ival = getint(&ptr); 349 if (dump_flag) { 350 printf("%#x:", ival); 351 break; 352 } 353 if (bit >= 0 && ((1 << bit) & mask) == 0) 354 break; 355 v = pnpval_as_int(cp + 2, pnpinfo); 356 if (verbose_flag) 357 printf("Matching %s (%c) table=%#x tomatch=%#x\n", 358 cp + 2, *cp, v, ival); 359 switch (*cp) { 360 case 'J': 361 if (ival == -1) 362 break; 363 /*FALLTHROUGH*/ 364 case 'I': 365 if (v != ival) 366 notme++; 367 break; 368 case 'G': 369 if (v < ival) 370 notme++; 371 break; 372 case 'L': 373 if (v > ival) 374 notme++; 375 break; 376 case 'M': 377 mask = ival; 378 break; 379 } 380 break; 381 /* String fields */ 382 case 'D': 383 case 'Z': 384 getstr(&ptr, val1); 385 if (dump_flag) { 386 printf("'%s':", val1); 387 break; 388 } 389 if (*cp == 'D') 390 break; 391 s = pnpval_as_str(cp + 2, pnpinfo); 392 if (strcmp(s, val1) != 0) 393 notme++; 394 break; 395 /* Key override fields, required to be last in the string */ 396 case 'T': 397 /* 398 * This is imperfect and only does one key and will be redone 399 * to be more general for multiple keys. Currently, nothing 400 * does that. 401 */ 402 if (dump_flag) /* No per-row data stored */ 403 break; 404 if (cp[strlen(cp) - 1] == ';') /* Skip required ; at end */ 405 cp[strlen(cp) - 1] = '\0'; /* in case it's not there */ 406 if ((s = strstr(pnpinfo, cp + 2)) == NULL) 407 notme++; 408 else if (s > pnpinfo && s[-1] != ' ') 409 notme++; 410 break; 411 default: 412 fprintf(stderr, "Unknown field type %c\n:", *cp); 413 break; 414 } 415 bit++; 416 cp = strchr(cp, ';'); 417 if (cp) 418 cp++; 419 } while (cp && *cp); 420 if (dump_flag) 421 printf("\n"); 422 else if (!notme) { 423 if (!unbound_flag) { 424 char *descr = NULL; 425 426 if (all_flag) 427 asprintf(&descr, "%s: %s", *dev ? dev : "unattached", lastmod); 428 else 429 asprintf(&descr, "%s", lastmod); 430 if (verbose_flag) 431 printf("Matches --- %s ---\n", lastmod); 432 433 if (descr != NULL && hit_index < DEVMATCH_MAX_HITS) { 434 match_data[hit_index].descr = descr; 435 match_data[hit_index].priority = bitrev16(mask); 436 hit_index++; 437 } else { 438 free(descr); 439 } 440 } 441 found++; 442 } 443 } 444 break; 445 default: 446 if (dump_flag) 447 printf("Unknown Type %d len %d\n", ival, len); 448 break; 449 } 450 walker = (void *)(len - sizeof(int) + (intptr_t)walker); 451 } 452 if (hit_index != 0) { 453 /* sort hits by priority */ 454 mergesort(match_data, hit_index, sizeof(match_data[0]), &match_data_compare); 455 456 /* printout */ 457 for (i = 0; i != hit_index; i++) { 458 puts(match_data[i].descr); 459 free(match_data[i].descr); 460 } 461 462 /* reset hit_index */ 463 hit_index = 0; 464 } 465 if (unbound_flag && found == 0 && *pnpinfo) { 466 if (verbose_flag) 467 printf("------------------------- "); 468 printf("%s on %s pnpinfo %s", *dev ? dev : "unattached", bus, pnpinfo); 469 if (verbose_flag) 470 printf(" -------------------------"); 471 printf("\n"); 472 } 473 free(lastmod); 474 } 475 476 static int 477 find_unmatched(struct devinfo_dev *dev, void *arg) 478 { 479 struct devinfo_dev *parent; 480 char *bus, *p; 481 482 do { 483 if (!all_flag && dev->dd_name[0] != '\0') 484 break; 485 if (!(dev->dd_flags & DF_ENABLED)) 486 break; 487 parent = devinfo_handle_to_device(dev->dd_parent); 488 bus = strdup(parent->dd_name); 489 p = bus + strlen(bus) - 1; 490 while (p >= bus && isdigit(*p)) 491 p--; 492 *++p = '\0'; 493 if (verbose_flag) 494 printf("Searching %s %s bus at %s for pnpinfo %s\n", 495 dev->dd_name, bus, dev->dd_location, dev->dd_pnpinfo); 496 search_hints(bus, dev->dd_name, dev->dd_pnpinfo); 497 free(bus); 498 } while (0); 499 500 return (devinfo_foreach_device_child(dev, find_unmatched, arg)); 501 } 502 503 static void 504 find_nomatch(char *nomatch) 505 { 506 char *bus, *pnpinfo, *tmp; 507 508 /* 509 * Find our bus name. It will include the unit number. We have to search 510 * backwards to avoid false positive for any PNP string that has ' on ' 511 * in them, which would come earlier in the string. Like if there were 512 * an 'Old Bard' ethernet card made by 'Stratford on Avon Hardware' or 513 * something silly like that. 514 */ 515 tmp = nomatch + strlen(nomatch) - 4; 516 while (tmp > nomatch && strncmp(tmp, " on ", 4) != 0) 517 tmp--; 518 if (tmp == nomatch) 519 errx(1, "No bus found in nomatch string: '%s'", nomatch); 520 bus = tmp + 4; 521 *tmp = '\0'; 522 tmp = bus + strlen(bus) - 1; 523 while (tmp > bus && isdigit(*tmp)) 524 tmp--; 525 *++tmp = '\0'; 526 527 /* 528 * Note: the NOMATCH events place both the bus location as well as the 529 * pnp info after the 'at' and we don't know where one stops and the 530 * other begins, so we pass the whole thing to our search routine. 531 */ 532 if (*nomatch == '?') 533 nomatch++; 534 if (strncmp(nomatch, " at ", 4) != 0) 535 errx(1, "Malformed NOMATCH string: '%s'", nomatch); 536 pnpinfo = nomatch + 4; 537 538 search_hints(bus, "", pnpinfo); 539 540 exit(0); 541 } 542 543 static void 544 usage(void) 545 { 546 547 errx(1, "devmatch [-adv] [-p nomatch] [-h linker-hints]"); 548 } 549 550 int 551 main(int argc, char **argv) 552 { 553 struct devinfo_dev *root; 554 int ch; 555 556 while ((ch = getopt_long(argc, argv, "adh:p:uv", 557 longopts, NULL)) != -1) { 558 switch (ch) { 559 case 'a': 560 all_flag++; 561 break; 562 case 'd': 563 dump_flag++; 564 break; 565 case 'h': 566 linker_hints = optarg; 567 break; 568 case 'p': 569 nomatch_str = optarg; 570 break; 571 case 'u': 572 unbound_flag++; 573 break; 574 case 'v': 575 verbose_flag++; 576 break; 577 default: 578 usage(); 579 } 580 } 581 argc -= optind; 582 argv += optind; 583 584 if (argc >= 1) 585 usage(); 586 587 read_linker_hints(); 588 if (dump_flag) { 589 search_hints(NULL, NULL, NULL); 590 exit(0); 591 } 592 593 if (nomatch_str != NULL) 594 find_nomatch(nomatch_str); 595 if (devinfo_init()) 596 err(1, "devinfo_init"); 597 if ((root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL) 598 errx(1, "can't find root device"); 599 devinfo_foreach_device_child(root, find_unmatched, (void *)0); 600 devinfo_free(); 601 } 602