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
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
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 *
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
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 *
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 *
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
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
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 *
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
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
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
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
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
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
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
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