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