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