#include <sys/types.h>
#include <sys/limits.h>
#include <assert.h>
#include <err.h>
#include <inttypes.h>
#include <libgeom.h>
#include <paths.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sysexits.h>
#include "hostres_snmp.h"
#include "hostres_oid.h"
#include "hostres_tree.h"
#define HR_FREEBSD_PART_TYPE 165
#define PART_STR_MLEN (128 + 1)
struct partition_entry {
asn_subid_t index[2];
u_char *label;
u_char *id;
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);
struct partition_map_entry {
int32_t index;
u_char *id;
struct partition_entry *entry;
STAILQ_ENTRY(partition_map_entry) link;
};
STAILQ_HEAD(partition_map, partition_map_entry);
static struct partition_map partition_map =
STAILQ_HEAD_INITIALIZER(partition_map);
static struct partition_tbl partition_tbl =
TAILQ_HEAD_INITIALIZER(partition_tbl);
static uint32_t next_partition_index = 1;
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);
}
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);
}
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;
assert(chunk_name != NULL);
if (chunk_name == NULL || chunk_name[0] == '\0')
return (NULL);
STAILQ_FOREACH(map, &partition_map, link)
if (strcmp(map->id, chunk_name) == 0)
break;
if (map == NULL) {
if (next_partition_index > INT_MAX) {
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));
entry->index[0] = ds_index;
entry->index[1] = map->index;
map->entry = entry;
if ((entry->id = strdup(map->id)) == NULL) {
free(entry);
return (NULL);
}
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);
}
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);
}
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);
}
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);
}
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;
k_size = chunk_size / 1024;
entry->size = (k_size > (off_t)INT_MAX ? INT_MAX : k_size);
}
void
partition_tbl_pre_refresh(void)
{
struct partition_entry *entry;
TAILQ_FOREACH(entry, &partition_tbl, link)
entry->flags &= ~HR_PARTITION_FOUND;
}
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);
}
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) {
if (strcmp(gp->lg_name, disk_dev_name) != 0)
continue;
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;
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);
}
}
}
}
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) {
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);
}
}
}
void
partition_tbl_handle_disk(int32_t ds_index, const char *disk_dev_name)
{
struct gmesh mesh;
struct gclass *classp;
int error;
assert(disk_dev_name != NULL);
assert(ds_index > 0);
HRDBG("===> getting partitions for %s <===", disk_dev_name);
if ((error = geom_gettree(&mesh)) != 0) {
syslog(LOG_WARNING, "cannot get GEOM tree: %m");
return;
}
if ((classp = find_class(&mesh, "MBR")) != NULL) {
get_mbr(classp, ds_index, disk_dev_name);
} else {
HRDBG("cannot find \"MBR\" geom class");
}
if ((classp = find_class(&mesh, "BSD")) != NULL) {
get_bsd_sun(classp, ds_index, disk_dev_name);
} else {
HRDBG("cannot find \"BSD\" geom class");
}
if ((classp = find_class(&mesh, "SUN")) != NULL) {
get_bsd_sun(classp, ds_index, disk_dev_name);
} else {
HRDBG("cannot find \"SUN\" geom class");
}
geom_deletetree(&mesh);
}
void
partition_tbl_post_refresh(void)
{
struct partition_entry *e, *etmp;
TAILQ_FOREACH_SAFE(e, &partition_tbl, link, etmp)
if (!(e->flags & HR_PARTITION_FOUND))
partition_entry_delete(e);
}
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));
}
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;
}
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_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();
}