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 */
59struct 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};
69TAILQ_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 */
75struct 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};
86STAILQ_HEAD(partition_map, partition_map_entry);
87
88/* Mapping table for consistent indexing */
89static struct partition_map partition_map =
90    STAILQ_HEAD_INITIALIZER(partition_map);
91
92/* THE partition table. */
93static struct partition_tbl partition_tbl =
94    TAILQ_HEAD_INITIALIZER(partition_tbl);
95
96/* next int available for indexing the hrPartitionTable */
97static uint32_t next_partition_index = 1;
98
99/*
100 * Partition_entry_cmp is used for INSERT_OBJECT_FUNC_LINK
101 * macro.
102 */
103static int
104partition_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 */
129static int
130partition_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 */
152static struct partition_entry *
153partition_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 */
252static void
253partition_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 */
273static struct partition_entry *
274partition_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 */
288static struct partition_entry *
289partition_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 */
305static void
306handle_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 */
335void
336partition_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 */
349static struct gclass *
350find_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 */
363static void
364get_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 */
413static void
414get_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 */
443void
444partition_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 */
500void
501partition_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 */
517void
518fini_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 */
541void
542handle_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 */
559int
560op_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