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