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