/*- * Copyright (c) 2005-2006 The FreeBSD Project * All rights reserved. * * Author: Victor Cruceru * * Redistribution of this software and documentation and use in source and * binary forms, with or without modification, are permitted provided that * the following conditions are met: * * 1. Redistributions of source code or documentation must retain the above * copyright notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * Host Resources MIB: hrPartitionTable implementation for SNMPd. */ #include #include #include #include #include #include #include #include #include #include #include #include "hostres_snmp.h" #include "hostres_oid.h" #include "hostres_tree.h" #ifdef PC98 #define HR_FREEBSD_PART_TYPE 0xc494 #else #define HR_FREEBSD_PART_TYPE 165 #endif /* Maximum length for label and id including \0 */ #define PART_STR_MLEN (128 + 1) /* * One row in the hrPartitionTable */ struct partition_entry { asn_subid_t index[2]; u_char *label; /* max allocated len will be PART_STR_MLEN */ u_char *id; /* max allocated len will be PART_STR_MLEN */ int32_t size; int32_t fs_Index; TAILQ_ENTRY(partition_entry) link; #define HR_PARTITION_FOUND 0x001 uint32_t flags; }; TAILQ_HEAD(partition_tbl, partition_entry); /* * This table is used to get a consistent indexing. It saves the name -> index * mapping while we rebuild the partition table. */ struct partition_map_entry { int32_t index; /* partition_entry::index */ u_char *id; /* max allocated len will be PART_STR_MLEN */ /* * next may be NULL if the respective partition_entry * is (temporally) gone. */ struct partition_entry *entry; STAILQ_ENTRY(partition_map_entry) link; }; STAILQ_HEAD(partition_map, partition_map_entry); /* Mapping table for consistent indexing */ static struct partition_map partition_map = STAILQ_HEAD_INITIALIZER(partition_map); /* THE partition table. */ static struct partition_tbl partition_tbl = TAILQ_HEAD_INITIALIZER(partition_tbl); /* next int available for indexing the hrPartitionTable */ static uint32_t next_partition_index = 1; /* * Partition_entry_cmp is used for INSERT_OBJECT_FUNC_LINK * macro. */ static int partition_entry_cmp(const struct partition_entry *a, const struct partition_entry *b) { assert(a != NULL); assert(b != NULL); if (a->index[0] < b->index[0]) return (-1); if (a->index[0] > b->index[0]) return (+1); if (a->index[1] < b->index[1]) return (-1); if (a->index[1] > b->index[1]) return (+1); return (0); } /* * Partition_idx_cmp is used for NEXT_OBJECT_FUNC and FIND_OBJECT_FUNC * macros */ static int partition_idx_cmp(const struct asn_oid *oid, u_int sub, const struct partition_entry *entry) { u_int i; for (i = 0; i < 2 && i < oid->len - sub; i++) { if (oid->subs[sub + i] < entry->index[i]) return (-1); if (oid->subs[sub + i] > entry->index[i]) return (+1); } if (oid->len - sub < 2) return (-1); if (oid->len - sub > 2) return (+1); return (0); } /** * Create a new partition table entry */ static struct partition_entry * partition_entry_create(int32_t ds_index, const char *chunk_name) { struct partition_entry *entry; struct partition_map_entry *map; size_t id_len; /* sanity checks */ assert(chunk_name != NULL); if (chunk_name == NULL || chunk_name[0] == '\0') return (NULL); /* check whether we already have seen this partition */ STAILQ_FOREACH(map, &partition_map, link) if (strcmp(map->id, chunk_name) == 0) break; if (map == NULL) { /* new object - get a new index and create a map */ if (next_partition_index > INT_MAX) { /* Unrecoverable error - die clean and quicly*/ syslog(LOG_ERR, "%s: hrPartitionTable index wrap", __func__); errx(EX_SOFTWARE, "hrPartitionTable index wrap"); } if ((map = malloc(sizeof(*map))) == NULL) { syslog(LOG_ERR, "hrPartitionTable: %s: %m", __func__); return (NULL); } id_len = strlen(chunk_name) + 1; if (id_len > PART_STR_MLEN) id_len = PART_STR_MLEN; if ((map->id = malloc(id_len)) == NULL) { free(map); return (NULL); } map->index = next_partition_index++; strlcpy(map->id, chunk_name, id_len); map->entry = NULL; STAILQ_INSERT_TAIL(&partition_map, map, link); HRDBG("%s added into hrPartitionMap at index=%d", chunk_name, map->index); } else { HRDBG("%s exists in hrPartitionMap index=%d", chunk_name, map->index); } if ((entry = malloc(sizeof(*entry))) == NULL) { syslog(LOG_WARNING, "hrPartitionTable: %s: %m", __func__); return (NULL); } memset(entry, 0, sizeof(*entry)); /* create the index */ entry->index[0] = ds_index; entry->index[1] = map->index; map->entry = entry; if ((entry->id = strdup(map->id)) == NULL) { free(entry); return (NULL); } /* * reuse id_len from here till the end of this function * for partition_entry::label */ id_len = strlen(_PATH_DEV) + strlen(chunk_name) + 1; if (id_len > PART_STR_MLEN) id_len = PART_STR_MLEN; if ((entry->label = malloc(id_len )) == NULL) { free(entry->id); free(entry); return (NULL); } snprintf(entry->label, id_len, "%s%s", _PATH_DEV, chunk_name); INSERT_OBJECT_FUNC_LINK(entry, &partition_tbl, link, partition_entry_cmp); return (entry); } /** * Delete a partition table entry but keep the map entry intact. */ static void partition_entry_delete(struct partition_entry *entry) { struct partition_map_entry *map; assert(entry != NULL); TAILQ_REMOVE(&partition_tbl, entry, link); STAILQ_FOREACH(map, &partition_map, link) if (map->entry == entry) { map->entry = NULL; break; } free(entry->id); free(entry->label); free(entry); } /** * Find a partition table entry by name. If none is found, return NULL. */ static struct partition_entry * partition_entry_find_by_name(const char *name) { struct partition_entry *entry = NULL; TAILQ_FOREACH(entry, &partition_tbl, link) if (strcmp(entry->id, name) == 0) return (entry); return (NULL); } /** * Find a partition table entry by label. If none is found, return NULL. */ static struct partition_entry * partition_entry_find_by_label(const char *name) { struct partition_entry *entry = NULL; TAILQ_FOREACH(entry, &partition_tbl, link) if (strcmp(entry->label, name) == 0) return (entry); return (NULL); } /** * Process a chunk from libgeom(4). A chunk is either a slice or a partition. * If necessary create a new partition table entry for it. In any case * set the size field of the entry and set the FOUND flag. */ static void handle_chunk(int32_t ds_index, const char *chunk_name, off_t chunk_size) { struct partition_entry *entry; daddr_t k_size; assert(chunk_name != NULL); assert(chunk_name[0] != '\0'); if (chunk_name == NULL || chunk_name[0] == '\0') return; HRDBG("ANALYZE chunk %s", chunk_name); if ((entry = partition_entry_find_by_name(chunk_name)) == NULL) if ((entry = partition_entry_create(ds_index, chunk_name)) == NULL) return; entry->flags |= HR_PARTITION_FOUND; /* actual size may overflow the SNMP type */ k_size = chunk_size / 1024; entry->size = (k_size > (off_t)INT_MAX ? INT_MAX : k_size); } /** * Start refreshing the partition table. A call to this function will * be followed by a call to handleDiskStorage() for every disk, followed * by a single call to the post_refresh function. */ void partition_tbl_pre_refresh(void) { struct partition_entry *entry; /* mark each entry as missing */ TAILQ_FOREACH(entry, &partition_tbl, link) entry->flags &= ~HR_PARTITION_FOUND; } /** * Try to find a geom(4) class by its name. Returns a pointer to that * class if found NULL otherways. */ static struct gclass * find_class(struct gmesh *mesh, const char *name) { struct gclass *classp; LIST_FOREACH(classp, &mesh->lg_class, lg_class) if (strcmp(classp->lg_name, name) == 0) return (classp); return (NULL); } /** * Process all MBR-type partitions from the given disk. */ static void get_mbr(struct gclass *classp, int32_t ds_index, const char *disk_dev_name) { struct ggeom *gp; struct gprovider *pp; struct gconfig *conf; long part_type; LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { /* We are only interested in partitions from this disk */ if (strcmp(gp->lg_name, disk_dev_name) != 0) continue; /* * Find all the non-BSD providers (these are handled in get_bsd) */ LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { LIST_FOREACH(conf, &pp->lg_config, lg_config) { if (conf->lg_name == NULL || conf->lg_val == NULL || strcmp(conf->lg_name, "type") != 0) continue; /* * We are not interested in BSD partitions * (ie ad0s1 is not interesting at this point). * We'll take care of them in detail (slice * by slice) in get_bsd. */ part_type = strtol(conf->lg_val, NULL, 10); if (part_type == HR_FREEBSD_PART_TYPE) break; HRDBG("-> MBR PROVIDER Name: %s", pp->lg_name); HRDBG("Mediasize: %jd", (intmax_t)pp->lg_mediasize / 1024); HRDBG("Sectorsize: %u", pp->lg_sectorsize); HRDBG("Mode: %s", pp->lg_mode); HRDBG("CONFIG: %s: %s", conf->lg_name, conf->lg_val); handle_chunk(ds_index, pp->lg_name, pp->lg_mediasize); } } } } /** * Process all BSD-type partitions from the given disk. */ static void get_bsd_sun(struct gclass *classp, int32_t ds_index, const char *disk_dev_name) { struct ggeom *gp; struct gprovider *pp; LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { /* * We are only interested in those geoms starting with * the disk_dev_name passed as parameter to this function. */ if (strncmp(gp->lg_name, disk_dev_name, strlen(disk_dev_name)) != 0) continue; LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { if (pp->lg_name == NULL) continue; handle_chunk(ds_index, pp->lg_name, pp->lg_mediasize); } } } /** * Called from the DiskStorage table for every row. Open the GEOM(4) framework * and process all the partitions in it. * ds_index is the index into the DiskStorage table. * This is done in two steps: for non BSD partitions the geom class "MBR" is * used, for our BSD slices the "BSD" geom class. */ void partition_tbl_handle_disk(int32_t ds_index, const char *disk_dev_name) { struct gmesh mesh; /* GEOM userland tree */ struct gclass *classp; int error; assert(disk_dev_name != NULL); assert(ds_index > 0); HRDBG("===> getting partitions for %s <===", disk_dev_name); /* try to construct the GEOM tree */ if ((error = geom_gettree(&mesh)) != 0) { syslog(LOG_WARNING, "cannot get GEOM tree: %m"); return; } /* * First try the GEOM "MBR" class. * This is needed for non-BSD slices (aka partitions) * on PC architectures. */ if ((classp = find_class(&mesh, "MBR")) != NULL) { get_mbr(classp, ds_index, disk_dev_name); } else { HRDBG("cannot find \"MBR\" geom class"); } /* * Get the "BSD" GEOM class. * Here we'll find all the info needed about the BSD slices. */ if ((classp = find_class(&mesh, "BSD")) != NULL) { get_bsd_sun(classp, ds_index, disk_dev_name); } else { /* no problem on sparc64 */ HRDBG("cannot find \"BSD\" geom class"); } /* * Get the "SUN" GEOM class. * Here we'll find all the info needed about the BSD slices. */ if ((classp = find_class(&mesh, "SUN")) != NULL) { get_bsd_sun(classp, ds_index, disk_dev_name); } else { /* no problem on i386 */ HRDBG("cannot find \"SUN\" geom class"); } geom_deletetree(&mesh); } /** * Finish refreshing the table. */ void partition_tbl_post_refresh(void) { struct partition_entry *e, *etmp; /* * Purge items that disappeared */ TAILQ_FOREACH_SAFE(e, &partition_tbl, link, etmp) if (!(e->flags & HR_PARTITION_FOUND)) partition_entry_delete(e); } /* * Finalization routine for hrPartitionTable * It destroys the lists and frees any allocated heap memory */ void fini_partition_tbl(void) { struct partition_map_entry *m; while ((m = STAILQ_FIRST(&partition_map)) != NULL) { STAILQ_REMOVE_HEAD(&partition_map, link); if(m->entry != NULL) { TAILQ_REMOVE(&partition_tbl, m->entry, link); free(m->entry->id); free(m->entry->label); free(m->entry); } free(m->id); free(m); } assert(TAILQ_EMPTY(&partition_tbl)); } /** * Called from the file system code to insert the file system table index * into the partition table entry. Note, that an partition table entry exists * only for local file systems. */ void handle_partition_fs_index(const char *name, int32_t fs_idx) { struct partition_entry *entry; if ((entry = partition_entry_find_by_label(name)) == NULL) { HRDBG("%s IS MISSING from hrPartitionTable", name); return; } HRDBG("%s [FS index = %d] IS in hrPartitionTable", name, fs_idx); entry->fs_Index = fs_idx; } /* * This is the implementation for a generated (by our SNMP tool) * function prototype, see hostres_tree.h * It handles the SNMP operations for hrPartitionTable */ int op_hrPartitionTable(struct snmp_context *ctx __unused, struct snmp_value *value, u_int sub, u_int iidx __unused, enum snmp_op op) { struct partition_entry *entry; /* * Refresh the disk storage table (which refreshes the partition * table) if necessary. */ refresh_disk_storage_tbl(0); switch (op) { case SNMP_OP_GETNEXT: if ((entry = NEXT_OBJECT_FUNC(&partition_tbl, &value->var, sub, partition_idx_cmp)) == NULL) return (SNMP_ERR_NOSUCHNAME); value->var.len = sub + 2; value->var.subs[sub] = entry->index[0]; value->var.subs[sub + 1] = entry->index[1]; goto get; case SNMP_OP_GET: if ((entry = FIND_OBJECT_FUNC(&partition_tbl, &value->var, sub, partition_idx_cmp)) == NULL) return (SNMP_ERR_NOSUCHNAME); goto get; case SNMP_OP_SET: if ((entry = FIND_OBJECT_FUNC(&partition_tbl, &value->var, sub, partition_idx_cmp)) == NULL) return (SNMP_ERR_NOT_WRITEABLE); return (SNMP_ERR_NO_CREATION); case SNMP_OP_ROLLBACK: case SNMP_OP_COMMIT: abort(); } abort(); get: switch (value->var.subs[sub - 1]) { case LEAF_hrPartitionIndex: value->v.integer = entry->index[1]; return (SNMP_ERR_NOERROR); case LEAF_hrPartitionLabel: return (string_get(value, entry->label, -1)); case LEAF_hrPartitionID: return(string_get(value, entry->id, -1)); case LEAF_hrPartitionSize: value->v.integer = entry->size; return (SNMP_ERR_NOERROR); case LEAF_hrPartitionFSIndex: value->v.integer = entry->fs_Index; return (SNMP_ERR_NOERROR); } abort(); }