hostres_partition_tbl.c revision 310899
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/11/usr.sbin/bsnmpd/modules/snmp_hostres/hostres_partition_tbl.c 310899 2016-12-31 10:28:59Z ngie $
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 */
65struct 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};
75TAILQ_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 */
81struct 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};
92STAILQ_HEAD(partition_map, partition_map_entry);
93
94/* Mapping table for consistent indexing */
95static struct partition_map partition_map =
96    STAILQ_HEAD_INITIALIZER(partition_map);
97
98/* THE partition table. */
99static struct partition_tbl partition_tbl =
100    TAILQ_HEAD_INITIALIZER(partition_tbl);
101
102/* next int available for indexing the hrPartitionTable */
103static uint32_t next_partition_index = 1;
104
105/*
106 * Partition_entry_cmp is used for INSERT_OBJECT_FUNC_LINK
107 * macro.
108 */
109static int
110partition_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 */
135static int
136partition_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 */
158static struct partition_entry *
159partition_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 */
258static void
259partition_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 */
279static struct partition_entry *
280partition_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 */
294static struct partition_entry *
295partition_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 */
311static void
312handle_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 */
341void
342partition_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 */
355static struct gclass *
356find_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 */
369static void
370get_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 */
419static void
420get_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 */
449void
450partition_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 */
506void
507partition_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 */
523void
524fini_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 */
547void
548handle_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 */
565int
566op_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