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