Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_device_tbl.c
107563 views
1
/*-
2
* Copyright (c) 2005-2006 The FreeBSD Project
3
* All rights reserved.
4
*
5
* Author: Victor Cruceru <[email protected]>
6
*
7
* Redistribution of this software and documentation and use in source and
8
* binary forms, with or without modification, are permitted provided that
9
* the following conditions are met:
10
*
11
* 1. Redistributions of source code or documentation must retain the above
12
* copyright notice, this list of conditions and the following disclaimer.
13
* 2. Redistributions in binary form must reproduce the above copyright
14
* notice, this list of conditions and the following disclaimer in the
15
* documentation and/or other materials provided with the distribution.
16
*
17
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
* SUCH DAMAGE.
28
*/
29
30
/*
31
* Host Resources MIB: hrDeviceTable implementation for SNMPd.
32
*/
33
34
#include <sys/un.h>
35
#include <sys/limits.h>
36
37
#include <assert.h>
38
#include <err.h>
39
#include <errno.h>
40
#include <stdlib.h>
41
#include <string.h>
42
#include <syslog.h>
43
#include <unistd.h>
44
#include <sysexits.h>
45
46
#include "hostres_snmp.h"
47
#include "hostres_oid.h"
48
#include "hostres_tree.h"
49
50
#define FREE_DEV_STRUCT(entry_p) do { \
51
free(entry_p->name); \
52
free(entry_p->location); \
53
free(entry_p->descr); \
54
free(entry_p); \
55
} while (0)
56
57
/*
58
* Status of a device
59
*/
60
enum DeviceStatus {
61
DS_UNKNOWN = 1,
62
DS_RUNNING = 2,
63
DS_WARNING = 3,
64
DS_TESTING = 4,
65
DS_DOWN = 5
66
};
67
68
TAILQ_HEAD(device_tbl, device_entry);
69
70
/* the head of the list with hrDeviceTable's entries */
71
static struct device_tbl device_tbl = TAILQ_HEAD_INITIALIZER(device_tbl);
72
73
/* Table used for consistent device table indexing. */
74
struct device_map device_map = STAILQ_HEAD_INITIALIZER(device_map);
75
76
/* next int available for indexing the hrDeviceTable */
77
static uint32_t next_device_index = 1;
78
79
/* last (agent) tick when hrDeviceTable was updated */
80
static uint64_t device_tick = 0;
81
82
/* maximum number of ticks between updates of device table */
83
uint32_t device_tbl_refresh = 10 * 100;
84
85
/* socket for /var/run/devd.pipe */
86
static int devd_sock = -1;
87
88
/* used to wait notifications from /var/run/devd.pipe */
89
static void *devd_fd;
90
91
/* some constants */
92
static const struct asn_oid OIDX_hrDeviceProcessor_c = OIDX_hrDeviceProcessor;
93
static const struct asn_oid OIDX_hrDeviceOther_c = OIDX_hrDeviceOther;
94
95
/**
96
* Create a new entry out of thin air.
97
*/
98
struct device_entry *
99
device_entry_create(const char *name, const char *location, const char *descr)
100
{
101
struct device_entry *entry = NULL;
102
struct device_map_entry *map = NULL;
103
size_t name_len;
104
size_t location_len;
105
106
assert((name[0] != 0) || (location[0] != 0));
107
108
if (name[0] == 0 && location[0] == 0)
109
return (NULL);
110
111
STAILQ_FOREACH(map, &device_map, link) {
112
assert(map->name_key != NULL);
113
assert(map->location_key != NULL);
114
115
if (strcmp(map->name_key, name) == 0 &&
116
strcmp(map->location_key, location) == 0) {
117
break;
118
}
119
}
120
121
if (map == NULL) {
122
/* new object - get a new index */
123
if (next_device_index > INT_MAX) {
124
syslog(LOG_ERR,
125
"%s: hrDeviceTable index wrap", __func__);
126
/* There isn't much we can do here.
127
* If the next_swins_index is consumed
128
* then we can't add entries to this table
129
* So it is better to exit - if the table is sparsed
130
* at the next agent run we can fill it fully.
131
*/
132
errx(EX_SOFTWARE, "hrDeviceTable index wrap");
133
/* not reachable */
134
}
135
136
if ((map = malloc(sizeof(*map))) == NULL) {
137
syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
138
return (NULL);
139
}
140
141
map->entry_p = NULL;
142
143
name_len = strlen(name) + 1;
144
if (name_len > DEV_NAME_MLEN)
145
name_len = DEV_NAME_MLEN;
146
147
if ((map->name_key = malloc(name_len)) == NULL) {
148
syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
149
free(map);
150
return (NULL);
151
}
152
153
location_len = strlen(location) + 1;
154
if (location_len > DEV_LOC_MLEN)
155
location_len = DEV_LOC_MLEN;
156
157
if ((map->location_key = malloc(location_len )) == NULL) {
158
syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
159
free(map->name_key);
160
free(map);
161
return (NULL);
162
}
163
164
map->hrIndex = next_device_index++;
165
166
strlcpy(map->name_key, name, name_len);
167
strlcpy(map->location_key, location, location_len);
168
169
STAILQ_INSERT_TAIL(&device_map, map, link);
170
HRDBG("%s at %s added into hrDeviceMap at index=%d",
171
name, location, map->hrIndex);
172
} else {
173
HRDBG("%s at %s exists in hrDeviceMap index=%d",
174
name, location, map->hrIndex);
175
}
176
177
if ((entry = malloc(sizeof(*entry))) == NULL) {
178
syslog(LOG_WARNING, "hrDeviceTable: %s: %m", __func__);
179
return (NULL);
180
}
181
memset(entry, 0, sizeof(*entry));
182
183
entry->index = map->hrIndex;
184
map->entry_p = entry;
185
186
if ((entry->name = strdup(map->name_key)) == NULL) {
187
syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
188
free(entry);
189
return (NULL);
190
}
191
192
if ((entry->location = strdup(map->location_key)) == NULL) {
193
syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
194
free(entry->name);
195
free(entry);
196
return (NULL);
197
}
198
199
/*
200
* From here till the end of this function we reuse name_len
201
* for a different purpose - for device_entry::descr
202
*/
203
if (name[0] != '\0')
204
name_len = strlen(name) + strlen(descr) +
205
strlen(": ") + 1;
206
else
207
name_len = strlen(location) + strlen(descr) +
208
strlen("unknown at : ") + 1;
209
210
if (name_len > DEV_DESCR_MLEN)
211
name_len = DEV_DESCR_MLEN;
212
213
if ((entry->descr = malloc(name_len )) == NULL) {
214
syslog(LOG_ERR, "hrDeviceTable: %s: %m", __func__ );
215
free(entry->name);
216
free(entry->location);
217
free(entry);
218
return (NULL);
219
}
220
221
memset(&entry->descr[0], '\0', name_len);
222
223
if (name[0] != '\0')
224
snprintf(entry->descr, name_len,
225
"%s: %s", name, descr);
226
else
227
snprintf(entry->descr, name_len,
228
"unknown at %s: %s", location, descr);
229
230
entry->id = &oid_zeroDotZero; /* unknown id - FIXME */
231
entry->status = (u_int)DS_UNKNOWN;
232
entry->errors = 0;
233
entry->type = &OIDX_hrDeviceOther_c;
234
235
INSERT_OBJECT_INT(entry, &device_tbl);
236
237
return (entry);
238
}
239
240
/**
241
* Create a new entry into the device table.
242
*/
243
static struct device_entry *
244
device_entry_create_devinfo(const struct devinfo_dev *dev_p)
245
{
246
247
assert(dev_p->dd_name != NULL);
248
assert(dev_p->dd_location != NULL);
249
250
return (device_entry_create(dev_p->dd_name, dev_p->dd_location,
251
dev_p->dd_desc));
252
}
253
254
/**
255
* Delete an entry from the device table.
256
*/
257
void
258
device_entry_delete(struct device_entry *entry)
259
{
260
struct device_map_entry *map;
261
262
assert(entry != NULL);
263
264
TAILQ_REMOVE(&device_tbl, entry, link);
265
266
STAILQ_FOREACH(map, &device_map, link)
267
if (map->entry_p == entry) {
268
map->entry_p = NULL;
269
break;
270
}
271
272
FREE_DEV_STRUCT(entry);
273
}
274
275
/**
276
* Find an entry given its name and location
277
*/
278
static struct device_entry *
279
device_find_by_dev(const struct devinfo_dev *dev_p)
280
{
281
struct device_map_entry *map;
282
283
assert(dev_p != NULL);
284
285
STAILQ_FOREACH(map, &device_map, link)
286
if (strcmp(map->name_key, dev_p->dd_name) == 0 &&
287
strcmp(map->location_key, dev_p->dd_location) == 0)
288
return (map->entry_p);
289
return (NULL);
290
}
291
292
/**
293
* Find an entry given its index.
294
*/
295
struct device_entry *
296
device_find_by_index(int32_t idx)
297
{
298
struct device_entry *entry;
299
300
TAILQ_FOREACH(entry, &device_tbl, link)
301
if (entry->index == idx)
302
return (entry);
303
return (NULL);
304
}
305
306
/**
307
* Find an device entry given its name.
308
*/
309
struct device_entry *
310
device_find_by_name(const char *dev_name)
311
{
312
struct device_map_entry *map;
313
314
assert(dev_name != NULL);
315
316
STAILQ_FOREACH(map, &device_map, link)
317
if (strcmp(map->name_key, dev_name) == 0)
318
return (map->entry_p);
319
320
return (NULL);
321
}
322
323
/**
324
* Find out the type of device. CPU only currently.
325
*/
326
static void
327
device_get_type(struct devinfo_dev *dev_p, const struct asn_oid **out_type_p)
328
{
329
330
assert(dev_p != NULL);
331
assert(out_type_p != NULL);
332
333
if (dev_p == NULL)
334
return;
335
336
if (strncmp(dev_p->dd_name, "cpu", strlen("cpu")) == 0 &&
337
strstr(dev_p->dd_location, ".CPU") != NULL) {
338
*out_type_p = &OIDX_hrDeviceProcessor_c;
339
return;
340
}
341
}
342
343
/**
344
* Get the status of a device
345
*/
346
static enum DeviceStatus
347
device_get_status(struct devinfo_dev *dev)
348
{
349
350
assert(dev != NULL);
351
352
switch (dev->dd_state) {
353
case DS_ALIVE: /* probe succeeded */
354
case DS_NOTPRESENT: /* not probed or probe failed */
355
return (DS_DOWN);
356
case DS_ATTACHED: /* attach method called */
357
return (DS_RUNNING);
358
default:
359
return (DS_UNKNOWN);
360
}
361
}
362
363
/**
364
* Get the info for the given device and then recursively process all
365
* child devices.
366
*/
367
static int
368
device_collector(struct devinfo_dev *dev, void *arg)
369
{
370
struct device_entry *entry;
371
372
HRDBG("%llu/%llu name='%s' desc='%s' drivername='%s' location='%s'",
373
(unsigned long long)dev->dd_handle,
374
(unsigned long long)dev->dd_parent, dev->dd_name, dev->dd_desc,
375
dev->dd_drivername, dev->dd_location);
376
377
if (dev->dd_name[0] != '\0' || dev->dd_location[0] != '\0') {
378
HRDBG("ANALYZING dev %s at %s",
379
dev->dd_name, dev->dd_location);
380
381
if ((entry = device_find_by_dev(dev)) != NULL) {
382
entry->flags |= HR_DEVICE_FOUND;
383
entry->status = (u_int)device_get_status(dev);
384
} else if ((entry = device_entry_create_devinfo(dev)) != NULL) {
385
device_get_type(dev, &entry->type);
386
387
entry->flags |= HR_DEVICE_FOUND;
388
entry->status = (u_int)device_get_status(dev);
389
}
390
} else {
391
HRDBG("SKIPPED unknown device at location '%s'",
392
dev->dd_location );
393
}
394
395
return (devinfo_foreach_device_child(dev, device_collector, arg));
396
}
397
398
/**
399
* Create the socket to the device daemon.
400
*/
401
static int
402
create_devd_socket(void)
403
{
404
int d_sock;
405
struct sockaddr_un devd_addr;
406
407
bzero(&devd_addr, sizeof(struct sockaddr_un));
408
409
if ((d_sock = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
410
syslog(LOG_ERR, "Failed to create the socket for %s: %m",
411
PATH_DEVD_PIPE);
412
return (-1);
413
}
414
415
devd_addr.sun_family = PF_LOCAL;
416
devd_addr.sun_len = sizeof(devd_addr);
417
strlcpy(devd_addr.sun_path, PATH_DEVD_PIPE,
418
sizeof(devd_addr.sun_path) - 1);
419
420
if (connect(d_sock, (struct sockaddr *)&devd_addr,
421
sizeof(devd_addr)) == -1) {
422
syslog(LOG_ERR,"Failed to connect socket for %s: %m",
423
PATH_DEVD_PIPE);
424
if (close(d_sock) < 0 )
425
syslog(LOG_ERR,"Failed to close socket for %s: %m",
426
PATH_DEVD_PIPE);
427
return (-1);
428
}
429
430
return (d_sock);
431
}
432
433
/*
434
* Event on the devd socket.
435
*
436
* We should probably directly process entries here. For simplicity just
437
* call the refresh routine with the force flag for now.
438
*/
439
static void
440
devd_socket_callback(int fd, void *arg __unused)
441
{
442
char buf[512];
443
int read_len = -1;
444
445
assert(fd == devd_sock);
446
447
HRDBG("called");
448
449
again:
450
read_len = read(fd, buf, sizeof(buf));
451
if (read_len < 0) {
452
if (errno == EBADF) {
453
devd_sock = -1;
454
if (devd_fd != NULL) {
455
fd_deselect(devd_fd);
456
devd_fd = NULL;
457
}
458
syslog(LOG_ERR, "Closing devd_fd, revert to "
459
"devinfo polling");
460
}
461
462
} else if (read_len == 0) {
463
syslog(LOG_ERR, "zero bytes read from devd pipe... "
464
"closing socket!");
465
466
if (close(devd_sock) < 0 )
467
syslog(LOG_ERR, "Failed to close devd socket: %m");
468
469
devd_sock = -1;
470
if (devd_fd != NULL) {
471
fd_deselect(devd_fd);
472
devd_fd = NULL;
473
}
474
syslog(LOG_ERR, "Closing devd_fd, revert to devinfo polling");
475
476
} else {
477
if (read_len == sizeof(buf))
478
goto again;
479
/* Only refresh device table on a device add or remove event. */
480
if (buf[0] == '+' || buf[0] == '-')
481
refresh_device_tbl(1);
482
}
483
}
484
485
/**
486
* Initialize and populate the device table.
487
*/
488
void
489
init_device_tbl(void)
490
{
491
492
/* initially populate table for the other tables */
493
refresh_device_tbl(1);
494
495
/* no problem if that fails - just use polling mode */
496
devd_sock = create_devd_socket();
497
}
498
499
/**
500
* Start devd(8) monitoring.
501
*/
502
void
503
start_device_tbl(struct lmodule *mod)
504
{
505
506
if (devd_sock > 0) {
507
devd_fd = fd_select(devd_sock, devd_socket_callback, NULL, mod);
508
if (devd_fd == NULL)
509
syslog(LOG_ERR, "fd_select failed on devd socket: %m");
510
}
511
}
512
513
/**
514
* Finalization routine for hrDeviceTable
515
* It destroys the lists and frees any allocated heap memory
516
*/
517
void
518
fini_device_tbl(void)
519
{
520
struct device_map_entry *n1;
521
522
if (devd_fd != NULL)
523
fd_deselect(devd_fd);
524
525
if (devd_sock != -1)
526
(void)close(devd_sock);
527
528
devinfo_free();
529
530
while ((n1 = STAILQ_FIRST(&device_map)) != NULL) {
531
STAILQ_REMOVE_HEAD(&device_map, link);
532
if (n1->entry_p != NULL) {
533
TAILQ_REMOVE(&device_tbl, n1->entry_p, link);
534
FREE_DEV_STRUCT(n1->entry_p);
535
}
536
free(n1->name_key);
537
free(n1->location_key);
538
free(n1);
539
}
540
assert(TAILQ_EMPTY(&device_tbl));
541
}
542
543
/**
544
* Refresh routine for hrDeviceTable. We don't refresh here if the devd socket
545
* is open, because in this case we have the actual information always. We
546
* also don't refresh when the table is new enough (if we don't have a devd
547
* socket). In either case a refresh can be forced by passing a non-zero value.
548
*/
549
void
550
refresh_device_tbl(int force)
551
{
552
struct device_entry *entry, *entry_tmp;
553
struct devinfo_dev *dev_root;
554
static int act = 0;
555
556
if (!force && (devd_sock >= 0 ||
557
(device_tick != 0 && this_tick - device_tick < device_tbl_refresh))){
558
HRDBG("no refresh needed");
559
return;
560
}
561
562
if (act) {
563
syslog(LOG_ERR, "%s: recursive call", __func__);
564
return;
565
}
566
567
if (devinfo_init() != 0) {
568
syslog(LOG_ERR,"%s: devinfo_init failed: %m", __func__);
569
return;
570
}
571
572
act = 1;
573
if ((dev_root = devinfo_handle_to_device(DEVINFO_ROOT_DEVICE)) == NULL){
574
syslog(LOG_ERR, "%s: can't get the root device: %m", __func__);
575
goto out;
576
}
577
578
/* mark each entry as missing */
579
TAILQ_FOREACH(entry, &device_tbl, link)
580
entry->flags &= ~HR_DEVICE_FOUND;
581
582
if (devinfo_foreach_device_child(dev_root, device_collector, NULL))
583
syslog(LOG_ERR, "%s: devinfo_foreach_device_child failed",
584
__func__);
585
586
/*
587
* Purge items that disappeared
588
*/
589
TAILQ_FOREACH_SAFE(entry, &device_tbl, link, entry_tmp) {
590
/*
591
* If HR_DEVICE_IMMUTABLE bit is set then this means that
592
* this entry was not detected by the above
593
* devinfo_foreach_device() call. So we are not deleting
594
* it there.
595
*/
596
if (!(entry->flags & HR_DEVICE_FOUND) &&
597
!(entry->flags & HR_DEVICE_IMMUTABLE))
598
device_entry_delete(entry);
599
}
600
601
device_tick = this_tick;
602
603
/*
604
* Force a refresh for the hrDiskStorageTable
605
* XXX Why not the other dependen tables?
606
*/
607
refresh_disk_storage_tbl(1);
608
609
out:
610
devinfo_free();
611
act = 0;
612
}
613
614
/**
615
* This is the implementation for a generated (by a SNMP tool)
616
* function prototype, see hostres_tree.h
617
* It handles the SNMP operations for hrDeviceTable
618
*/
619
int
620
op_hrDeviceTable(struct snmp_context *ctx __unused, struct snmp_value *value,
621
u_int sub, u_int iidx __unused, enum snmp_op curr_op)
622
{
623
struct device_entry *entry;
624
625
refresh_device_tbl(0);
626
627
switch (curr_op) {
628
629
case SNMP_OP_GETNEXT:
630
if ((entry = NEXT_OBJECT_INT(&device_tbl,
631
&value->var, sub)) == NULL)
632
return (SNMP_ERR_NOSUCHNAME);
633
value->var.len = sub + 1;
634
value->var.subs[sub] = entry->index;
635
goto get;
636
637
case SNMP_OP_GET:
638
if ((entry = FIND_OBJECT_INT(&device_tbl,
639
&value->var, sub)) == NULL)
640
return (SNMP_ERR_NOSUCHNAME);
641
goto get;
642
643
case SNMP_OP_SET:
644
if ((entry = FIND_OBJECT_INT(&device_tbl,
645
&value->var, sub)) == NULL)
646
return (SNMP_ERR_NO_CREATION);
647
return (SNMP_ERR_NOT_WRITEABLE);
648
649
case SNMP_OP_ROLLBACK:
650
case SNMP_OP_COMMIT:
651
abort();
652
}
653
abort();
654
655
get:
656
switch (value->var.subs[sub - 1]) {
657
658
case LEAF_hrDeviceIndex:
659
value->v.integer = entry->index;
660
return (SNMP_ERR_NOERROR);
661
662
case LEAF_hrDeviceType:
663
assert(entry->type != NULL);
664
value->v.oid = *(entry->type);
665
return (SNMP_ERR_NOERROR);
666
667
case LEAF_hrDeviceDescr:
668
return (string_get(value, entry->descr, -1));
669
670
case LEAF_hrDeviceID:
671
value->v.oid = *(entry->id);
672
return (SNMP_ERR_NOERROR);
673
674
case LEAF_hrDeviceStatus:
675
value->v.integer = entry->status;
676
return (SNMP_ERR_NOERROR);
677
678
case LEAF_hrDeviceErrors:
679
value->v.uint32 = entry->errors;
680
return (SNMP_ERR_NOERROR);
681
}
682
abort();
683
}
684
685