1 /*-
2 * Copyright (c) 2005-2006 The FreeBSD Project
3 * All rights reserved.
4 *
5 * Author: Victor Cruceru <soc-victor@freebsd.org>
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 * $FreeBSD: stable/12/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_partition_tbl.c 344747 2019-03-04 03:38:43Z avos $
30 */
31
32 /*
33 * Host Resources MIB: hrPartitionTable implementation for SNMPd.
34 */
35
36 #include <sys/types.h>
37 #include <sys/limits.h>
38
39 #include <assert.h>
40 #include <err.h>
41 #include <inttypes.h>
42 #include <libgeom.h>
43 #include <paths.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <syslog.h>
47 #include <sysexits.h>
48
49 #include "hostres_snmp.h"
50 #include "hostres_oid.h"
51 #include "hostres_tree.h"
52
53 #define HR_FREEBSD_PART_TYPE 165
54
55 /* Maximum length for label and id including \0 */
56 #define PART_STR_MLEN (128 + 1)
57
58 /*
59 * One row in the hrPartitionTable
60 */
61 struct partition_entry {
62 asn_subid_t index[2];
63 u_char *label; /* max allocated len will be PART_STR_MLEN */
64 u_char *id; /* max allocated len will be PART_STR_MLEN */
65 int32_t size;
66 int32_t fs_Index;
67 TAILQ_ENTRY(partition_entry) link;
68 #define HR_PARTITION_FOUND 0x001
69 uint32_t flags;
70 };
71 TAILQ_HEAD(partition_tbl, partition_entry);
72
73 /*
74 * This table is used to get a consistent indexing. It saves the name -> index
75 * mapping while we rebuild the partition table.
76 */
77 struct partition_map_entry {
78 int32_t index; /* partition_entry::index */
79 u_char *id; /* max allocated len will be PART_STR_MLEN */
80
81 /*
82 * next may be NULL if the respective partition_entry
83 * is (temporally) gone.
84 */
85 struct partition_entry *entry;
86 STAILQ_ENTRY(partition_map_entry) link;
87 };
88 STAILQ_HEAD(partition_map, partition_map_entry);
89
90 /* Mapping table for consistent indexing */
91 static struct partition_map partition_map =
92 STAILQ_HEAD_INITIALIZER(partition_map);
93
94 /* THE partition table. */
95 static struct partition_tbl partition_tbl =
96 TAILQ_HEAD_INITIALIZER(partition_tbl);
97
98 /* next int available for indexing the hrPartitionTable */
99 static uint32_t next_partition_index = 1;
100
101 /*
102 * Partition_entry_cmp is used for INSERT_OBJECT_FUNC_LINK
103 * macro.
104 */
105 static int
partition_entry_cmp(const struct partition_entry * a,const struct partition_entry * b)106 partition_entry_cmp(const struct partition_entry *a,
107 const struct partition_entry *b)
108 {
109 assert(a != NULL);
110 assert(b != NULL);
111
112 if (a->index[0] < b->index[0])
113 return (-1);
114
115 if (a->index[0] > b->index[0])
116 return (+1);
117
118 if (a->index[1] < b->index[1])
119 return (-1);
120
121 if (a->index[1] > b->index[1])
122 return (+1);
123
124 return (0);
125 }
126
127 /*
128 * Partition_idx_cmp is used for NEXT_OBJECT_FUNC and FIND_OBJECT_FUNC
129 * macros
130 */
131 static int
partition_idx_cmp(const struct asn_oid * oid,u_int sub,const struct partition_entry * entry)132 partition_idx_cmp(const struct asn_oid *oid, u_int sub,
133 const struct partition_entry *entry)
134 {
135 u_int i;
136
137 for (i = 0; i < 2 && i < oid->len - sub; i++) {
138 if (oid->subs[sub + i] < entry->index[i])
139 return (-1);
140 if (oid->subs[sub + i] > entry->index[i])
141 return (+1);
142 }
143 if (oid->len - sub < 2)
144 return (-1);
145 if (oid->len - sub > 2)
146 return (+1);
147
148 return (0);
149 }
150
151 /**
152 * Create a new partition table entry
153 */
154 static struct partition_entry *
partition_entry_create(int32_t ds_index,const char * chunk_name)155 partition_entry_create(int32_t ds_index, const char *chunk_name)
156 {
157 struct partition_entry *entry;
158 struct partition_map_entry *map;
159 size_t id_len;
160
161 /* sanity checks */
162 assert(chunk_name != NULL);
163 if (chunk_name == NULL || chunk_name[0] == '\0')
164 return (NULL);
165
166 /* check whether we already have seen this partition */
167 STAILQ_FOREACH(map, &partition_map, link)
168 if (strcmp(map->id, chunk_name) == 0)
169 break;
170
171 if (map == NULL) {
172 /* new object - get a new index and create a map */
173
174 if (next_partition_index > INT_MAX) {
175 /* Unrecoverable error - die clean and quicly*/
176 syslog(LOG_ERR, "%s: hrPartitionTable index wrap",
177 __func__);
178 errx(EX_SOFTWARE, "hrPartitionTable index wrap");
179 }
180
181 if ((map = malloc(sizeof(*map))) == NULL) {
182 syslog(LOG_ERR, "hrPartitionTable: %s: %m", __func__);
183 return (NULL);
184 }
185
186 id_len = strlen(chunk_name) + 1;
187 if (id_len > PART_STR_MLEN)
188 id_len = PART_STR_MLEN;
189
190 if ((map->id = malloc(id_len)) == NULL) {
191 free(map);
192 return (NULL);
193 }
194
195 map->index = next_partition_index++;
196
197 strlcpy(map->id, chunk_name, id_len);
198
199 map->entry = NULL;
200
201 STAILQ_INSERT_TAIL(&partition_map, map, link);
202
203 HRDBG("%s added into hrPartitionMap at index=%d",
204 chunk_name, map->index);
205
206 } else {
207 HRDBG("%s exists in hrPartitionMap index=%d",
208 chunk_name, map->index);
209 }
210
211 if ((entry = malloc(sizeof(*entry))) == NULL) {
212 syslog(LOG_WARNING, "hrPartitionTable: %s: %m", __func__);
213 return (NULL);
214 }
215 memset(entry, 0, sizeof(*entry));
216
217 /* create the index */
218 entry->index[0] = ds_index;
219 entry->index[1] = map->index;
220
221 map->entry = entry;
222
223 if ((entry->id = strdup(map->id)) == NULL) {
224 free(entry);
225 return (NULL);
226 }
227
228 /*
229 * reuse id_len from here till the end of this function
230 * for partition_entry::label
231 */
232 id_len = strlen(_PATH_DEV) + strlen(chunk_name) + 1;
233
234 if (id_len > PART_STR_MLEN)
235 id_len = PART_STR_MLEN;
236
237 if ((entry->label = malloc(id_len )) == NULL) {
238 free(entry->id);
239 free(entry);
240 return (NULL);
241 }
242
243 snprintf(entry->label, id_len, "%s%s", _PATH_DEV, chunk_name);
244
245 INSERT_OBJECT_FUNC_LINK(entry, &partition_tbl, link,
246 partition_entry_cmp);
247
248 return (entry);
249 }
250
251 /**
252 * Delete a partition table entry but keep the map entry intact.
253 */
254 static void
partition_entry_delete(struct partition_entry * entry)255 partition_entry_delete(struct partition_entry *entry)
256 {
257 struct partition_map_entry *map;
258
259 assert(entry != NULL);
260
261 TAILQ_REMOVE(&partition_tbl, entry, link);
262 STAILQ_FOREACH(map, &partition_map, link)
263 if (map->entry == entry) {
264 map->entry = NULL;
265 break;
266 }
267 free(entry->id);
268 free(entry->label);
269 free(entry);
270 }
271
272 /**
273 * Find a partition table entry by name. If none is found, return NULL.
274 */
275 static struct partition_entry *
partition_entry_find_by_name(const char * name)276 partition_entry_find_by_name(const char *name)
277 {
278 struct partition_entry *entry = NULL;
279
280 TAILQ_FOREACH(entry, &partition_tbl, link)
281 if (strcmp(entry->id, name) == 0)
282 return (entry);
283
284 return (NULL);
285 }
286
287 /**
288 * Find a partition table entry by label. If none is found, return NULL.
289 */
290 static struct partition_entry *
partition_entry_find_by_label(const char * name)291 partition_entry_find_by_label(const char *name)
292 {
293 struct partition_entry *entry = NULL;
294
295 TAILQ_FOREACH(entry, &partition_tbl, link)
296 if (strcmp(entry->label, name) == 0)
297 return (entry);
298
299 return (NULL);
300 }
301
302 /**
303 * Process a chunk from libgeom(4). A chunk is either a slice or a partition.
304 * If necessary create a new partition table entry for it. In any case
305 * set the size field of the entry and set the FOUND flag.
306 */
307 static void
handle_chunk(int32_t ds_index,const char * chunk_name,off_t chunk_size)308 handle_chunk(int32_t ds_index, const char *chunk_name, off_t chunk_size)
309 {
310 struct partition_entry *entry;
311 daddr_t k_size;
312
313 assert(chunk_name != NULL);
314 assert(chunk_name[0] != '\0');
315 if (chunk_name == NULL || chunk_name[0] == '\0')
316 return;
317
318 HRDBG("ANALYZE chunk %s", chunk_name);
319
320 if ((entry = partition_entry_find_by_name(chunk_name)) == NULL)
321 if ((entry = partition_entry_create(ds_index,
322 chunk_name)) == NULL)
323 return;
324
325 entry->flags |= HR_PARTITION_FOUND;
326
327 /* actual size may overflow the SNMP type */
328 k_size = chunk_size / 1024;
329 entry->size = (k_size > (off_t)INT_MAX ? INT_MAX : k_size);
330 }
331
332 /**
333 * Start refreshing the partition table. A call to this function will
334 * be followed by a call to handleDiskStorage() for every disk, followed
335 * by a single call to the post_refresh function.
336 */
337 void
partition_tbl_pre_refresh(void)338 partition_tbl_pre_refresh(void)
339 {
340 struct partition_entry *entry;
341
342 /* mark each entry as missing */
343 TAILQ_FOREACH(entry, &partition_tbl, link)
344 entry->flags &= ~HR_PARTITION_FOUND;
345 }
346
347 /**
348 * Try to find a geom(4) class by its name. Returns a pointer to that
349 * class if found NULL otherways.
350 */
351 static struct gclass *
find_class(struct gmesh * mesh,const char * name)352 find_class(struct gmesh *mesh, const char *name)
353 {
354 struct gclass *classp;
355
356 LIST_FOREACH(classp, &mesh->lg_class, lg_class)
357 if (strcmp(classp->lg_name, name) == 0)
358 return (classp);
359 return (NULL);
360 }
361
362 /**
363 * Process all MBR-type partitions from the given disk.
364 */
365 static void
get_mbr(struct gclass * classp,int32_t ds_index,const char * disk_dev_name)366 get_mbr(struct gclass *classp, int32_t ds_index, const char *disk_dev_name)
367 {
368 struct ggeom *gp;
369 struct gprovider *pp;
370 struct gconfig *conf;
371 long part_type;
372
373 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
374 /* We are only interested in partitions from this disk */
375 if (strcmp(gp->lg_name, disk_dev_name) != 0)
376 continue;
377
378 /*
379 * Find all the non-BSD providers (these are handled in get_bsd)
380 */
381 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
382 LIST_FOREACH(conf, &pp->lg_config, lg_config) {
383 if (conf->lg_name == NULL ||
384 conf->lg_val == NULL ||
385 strcmp(conf->lg_name, "type") != 0)
386 continue;
387
388 /*
389 * We are not interested in BSD partitions
390 * (ie ad0s1 is not interesting at this point).
391 * We'll take care of them in detail (slice
392 * by slice) in get_bsd.
393 */
394 part_type = strtol(conf->lg_val, NULL, 10);
395 if (part_type == HR_FREEBSD_PART_TYPE)
396 break;
397 HRDBG("-> MBR PROVIDER Name: %s", pp->lg_name);
398 HRDBG("Mediasize: %jd",
399 (intmax_t)pp->lg_mediasize / 1024);
400 HRDBG("Sectorsize: %u", pp->lg_sectorsize);
401 HRDBG("Mode: %s", pp->lg_mode);
402 HRDBG("CONFIG: %s: %s",
403 conf->lg_name, conf->lg_val);
404
405 handle_chunk(ds_index, pp->lg_name,
406 pp->lg_mediasize);
407 }
408 }
409 }
410 }
411
412 /**
413 * Process all BSD-type partitions from the given disk.
414 */
415 static void
get_bsd_sun(struct gclass * classp,int32_t ds_index,const char * disk_dev_name)416 get_bsd_sun(struct gclass *classp, int32_t ds_index, const char *disk_dev_name)
417 {
418 struct ggeom *gp;
419 struct gprovider *pp;
420
421 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) {
422 /*
423 * We are only interested in those geoms starting with
424 * the disk_dev_name passed as parameter to this function.
425 */
426 if (strncmp(gp->lg_name, disk_dev_name,
427 strlen(disk_dev_name)) != 0)
428 continue;
429
430 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) {
431 if (pp->lg_name == NULL)
432 continue;
433 handle_chunk(ds_index, pp->lg_name, pp->lg_mediasize);
434 }
435 }
436 }
437
438 /**
439 * Called from the DiskStorage table for every row. Open the GEOM(4) framework
440 * and process all the partitions in it.
441 * ds_index is the index into the DiskStorage table.
442 * This is done in two steps: for non BSD partitions the geom class "MBR" is
443 * used, for our BSD slices the "BSD" geom class.
444 */
445 void
partition_tbl_handle_disk(int32_t ds_index,const char * disk_dev_name)446 partition_tbl_handle_disk(int32_t ds_index, const char *disk_dev_name)
447 {
448 struct gmesh mesh; /* GEOM userland tree */
449 struct gclass *classp;
450 int error;
451
452 assert(disk_dev_name != NULL);
453 assert(ds_index > 0);
454
455 HRDBG("===> getting partitions for %s <===", disk_dev_name);
456
457 /* try to construct the GEOM tree */
458 if ((error = geom_gettree(&mesh)) != 0) {
459 syslog(LOG_WARNING, "cannot get GEOM tree: %m");
460 return;
461 }
462
463 /*
464 * First try the GEOM "MBR" class.
465 * This is needed for non-BSD slices (aka partitions)
466 * on PC architectures.
467 */
468 if ((classp = find_class(&mesh, "MBR")) != NULL) {
469 get_mbr(classp, ds_index, disk_dev_name);
470 } else {
471 HRDBG("cannot find \"MBR\" geom class");
472 }
473
474 /*
475 * Get the "BSD" GEOM class.
476 * Here we'll find all the info needed about the BSD slices.
477 */
478 if ((classp = find_class(&mesh, "BSD")) != NULL) {
479 get_bsd_sun(classp, ds_index, disk_dev_name);
480 } else {
481 /* no problem on sparc64 */
482 HRDBG("cannot find \"BSD\" geom class");
483 }
484
485 /*
486 * Get the "SUN" GEOM class.
487 * Here we'll find all the info needed about the BSD slices.
488 */
489 if ((classp = find_class(&mesh, "SUN")) != NULL) {
490 get_bsd_sun(classp, ds_index, disk_dev_name);
491 } else {
492 /* no problem on i386 */
493 HRDBG("cannot find \"SUN\" geom class");
494 }
495
496 geom_deletetree(&mesh);
497 }
498
499 /**
500 * Finish refreshing the table.
501 */
502 void
partition_tbl_post_refresh(void)503 partition_tbl_post_refresh(void)
504 {
505 struct partition_entry *e, *etmp;
506
507 /*
508 * Purge items that disappeared
509 */
510 TAILQ_FOREACH_SAFE(e, &partition_tbl, link, etmp)
511 if (!(e->flags & HR_PARTITION_FOUND))
512 partition_entry_delete(e);
513 }
514
515 /*
516 * Finalization routine for hrPartitionTable
517 * It destroys the lists and frees any allocated heap memory
518 */
519 void
fini_partition_tbl(void)520 fini_partition_tbl(void)
521 {
522 struct partition_map_entry *m;
523
524 while ((m = STAILQ_FIRST(&partition_map)) != NULL) {
525 STAILQ_REMOVE_HEAD(&partition_map, link);
526 if(m->entry != NULL) {
527 TAILQ_REMOVE(&partition_tbl, m->entry, link);
528 free(m->entry->id);
529 free(m->entry->label);
530 free(m->entry);
531 }
532 free(m->id);
533 free(m);
534 }
535 assert(TAILQ_EMPTY(&partition_tbl));
536 }
537
538 /**
539 * Called from the file system code to insert the file system table index
540 * into the partition table entry. Note, that an partition table entry exists
541 * only for local file systems.
542 */
543 void
handle_partition_fs_index(const char * name,int32_t fs_idx)544 handle_partition_fs_index(const char *name, int32_t fs_idx)
545 {
546 struct partition_entry *entry;
547
548 if ((entry = partition_entry_find_by_label(name)) == NULL) {
549 HRDBG("%s IS MISSING from hrPartitionTable", name);
550 return;
551 }
552 HRDBG("%s [FS index = %d] IS in hrPartitionTable", name, fs_idx);
553 entry->fs_Index = fs_idx;
554 }
555
556 /*
557 * This is the implementation for a generated (by our SNMP tool)
558 * function prototype, see hostres_tree.h
559 * It handles the SNMP operations for hrPartitionTable
560 */
561 int
op_hrPartitionTable(struct snmp_context * ctx __unused,struct snmp_value * value,u_int sub,u_int iidx __unused,enum snmp_op op)562 op_hrPartitionTable(struct snmp_context *ctx __unused, struct snmp_value *value,
563 u_int sub, u_int iidx __unused, enum snmp_op op)
564 {
565 struct partition_entry *entry;
566
567 /*
568 * Refresh the disk storage table (which refreshes the partition
569 * table) if necessary.
570 */
571 refresh_disk_storage_tbl(0);
572
573 switch (op) {
574
575 case SNMP_OP_GETNEXT:
576 if ((entry = NEXT_OBJECT_FUNC(&partition_tbl,
577 &value->var, sub, partition_idx_cmp)) == NULL)
578 return (SNMP_ERR_NOSUCHNAME);
579
580 value->var.len = sub + 2;
581 value->var.subs[sub] = entry->index[0];
582 value->var.subs[sub + 1] = entry->index[1];
583
584 goto get;
585
586 case SNMP_OP_GET:
587 if ((entry = FIND_OBJECT_FUNC(&partition_tbl,
588 &value->var, sub, partition_idx_cmp)) == NULL)
589 return (SNMP_ERR_NOSUCHNAME);
590 goto get;
591
592 case SNMP_OP_SET:
593 if ((entry = FIND_OBJECT_FUNC(&partition_tbl,
594 &value->var, sub, partition_idx_cmp)) == NULL)
595 return (SNMP_ERR_NOT_WRITEABLE);
596 return (SNMP_ERR_NO_CREATION);
597
598 case SNMP_OP_ROLLBACK:
599 case SNMP_OP_COMMIT:
600 abort();
601 }
602 abort();
603
604 get:
605 switch (value->var.subs[sub - 1]) {
606
607 case LEAF_hrPartitionIndex:
608 value->v.integer = entry->index[1];
609 return (SNMP_ERR_NOERROR);
610
611 case LEAF_hrPartitionLabel:
612 return (string_get(value, entry->label, -1));
613
614 case LEAF_hrPartitionID:
615 return(string_get(value, entry->id, -1));
616
617 case LEAF_hrPartitionSize:
618 value->v.integer = entry->size;
619 return (SNMP_ERR_NOERROR);
620
621 case LEAF_hrPartitionFSIndex:
622 value->v.integer = entry->fs_Index;
623 return (SNMP_ERR_NOERROR);
624 }
625 abort();
626 }
627