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$ 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 #define HR_FREEBSD_PART_TYPE 165 54 55 /* Maximum length for label and id including \0 */ 56 #define PART_STR_MLEN (128 + 1) 57 58 /* 59 * One row in the hrPartitionTable 60 */ 61 struct partition_entry { 62 asn_subid_t index[2]; 63 u_char *label; /* max allocated len will be PART_STR_MLEN */ 64 u_char *id; /* max allocated len will be PART_STR_MLEN */ 65 int32_t size; 66 int32_t fs_Index; 67 TAILQ_ENTRY(partition_entry) link; 68 #define HR_PARTITION_FOUND 0x001 69 uint32_t flags; 70 }; 71 TAILQ_HEAD(partition_tbl, partition_entry); 72 73 /* 74 * This table is used to get a consistent indexing. It saves the name -> index 75 * mapping while we rebuild the partition table. 76 */ 77 struct partition_map_entry { 78 int32_t index; /* partition_entry::index */ 79 u_char *id; /* max allocated len will be PART_STR_MLEN */ 80 81 /* 82 * next may be NULL if the respective partition_entry 83 * is (temporally) gone. 84 */ 85 struct partition_entry *entry; 86 STAILQ_ENTRY(partition_map_entry) link; 87 }; 88 STAILQ_HEAD(partition_map, partition_map_entry); 89 90 /* Mapping table for consistent indexing */ 91 static struct partition_map partition_map = 92 STAILQ_HEAD_INITIALIZER(partition_map); 93 94 /* THE partition table. */ 95 static struct partition_tbl partition_tbl = 96 TAILQ_HEAD_INITIALIZER(partition_tbl); 97 98 /* next int available for indexing the hrPartitionTable */ 99 static uint32_t next_partition_index = 1; 100 101 /* 102 * Partition_entry_cmp is used for INSERT_OBJECT_FUNC_LINK 103 * macro. 104 */ 105 static int 106 partition_entry_cmp(const struct partition_entry *a, 107 const struct partition_entry *b) 108 { 109 assert(a != NULL); 110 assert(b != NULL); 111 112 if (a->index[0] < b->index[0]) 113 return (-1); 114 115 if (a->index[0] > b->index[0]) 116 return (+1); 117 118 if (a->index[1] < b->index[1]) 119 return (-1); 120 121 if (a->index[1] > b->index[1]) 122 return (+1); 123 124 return (0); 125 } 126 127 /* 128 * Partition_idx_cmp is used for NEXT_OBJECT_FUNC and FIND_OBJECT_FUNC 129 * macros 130 */ 131 static int 132 partition_idx_cmp(const struct asn_oid *oid, u_int sub, 133 const struct partition_entry *entry) 134 { 135 u_int i; 136 137 for (i = 0; i < 2 && i < oid->len - sub; i++) { 138 if (oid->subs[sub + i] < entry->index[i]) 139 return (-1); 140 if (oid->subs[sub + i] > entry->index[i]) 141 return (+1); 142 } 143 if (oid->len - sub < 2) 144 return (-1); 145 if (oid->len - sub > 2) 146 return (+1); 147 148 return (0); 149 } 150 151 /** 152 * Create a new partition table entry 153 */ 154 static struct partition_entry * 155 partition_entry_create(int32_t ds_index, const char *chunk_name) 156 { 157 struct partition_entry *entry; 158 struct partition_map_entry *map; 159 size_t id_len; 160 161 /* sanity checks */ 162 assert(chunk_name != NULL); 163 if (chunk_name == NULL || chunk_name[0] == '\0') 164 return (NULL); 165 166 /* check whether we already have seen this partition */ 167 STAILQ_FOREACH(map, &partition_map, link) 168 if (strcmp(map->id, chunk_name) == 0) 169 break; 170 171 if (map == NULL) { 172 /* new object - get a new index and create a map */ 173 174 if (next_partition_index > INT_MAX) { 175 /* Unrecoverable error - die clean and quicly*/ 176 syslog(LOG_ERR, "%s: hrPartitionTable index wrap", 177 __func__); 178 errx(EX_SOFTWARE, "hrPartitionTable index wrap"); 179 } 180 181 if ((map = malloc(sizeof(*map))) == NULL) { 182 syslog(LOG_ERR, "hrPartitionTable: %s: %m", __func__); 183 return (NULL); 184 } 185 186 id_len = strlen(chunk_name) + 1; 187 if (id_len > PART_STR_MLEN) 188 id_len = PART_STR_MLEN; 189 190 if ((map->id = malloc(id_len)) == NULL) { 191 free(map); 192 return (NULL); 193 } 194 195 map->index = next_partition_index++; 196 197 strlcpy(map->id, chunk_name, id_len); 198 199 map->entry = NULL; 200 201 STAILQ_INSERT_TAIL(&partition_map, map, link); 202 203 HRDBG("%s added into hrPartitionMap at index=%d", 204 chunk_name, map->index); 205 206 } else { 207 HRDBG("%s exists in hrPartitionMap index=%d", 208 chunk_name, map->index); 209 } 210 211 if ((entry = malloc(sizeof(*entry))) == NULL) { 212 syslog(LOG_WARNING, "hrPartitionTable: %s: %m", __func__); 213 return (NULL); 214 } 215 memset(entry, 0, sizeof(*entry)); 216 217 /* create the index */ 218 entry->index[0] = ds_index; 219 entry->index[1] = map->index; 220 221 map->entry = entry; 222 223 if ((entry->id = strdup(map->id)) == NULL) { 224 free(entry); 225 return (NULL); 226 } 227 228 /* 229 * reuse id_len from here till the end of this function 230 * for partition_entry::label 231 */ 232 id_len = strlen(_PATH_DEV) + strlen(chunk_name) + 1; 233 234 if (id_len > PART_STR_MLEN) 235 id_len = PART_STR_MLEN; 236 237 if ((entry->label = malloc(id_len )) == NULL) { 238 free(entry->id); 239 free(entry); 240 return (NULL); 241 } 242 243 snprintf(entry->label, id_len, "%s%s", _PATH_DEV, chunk_name); 244 245 INSERT_OBJECT_FUNC_LINK(entry, &partition_tbl, link, 246 partition_entry_cmp); 247 248 return (entry); 249 } 250 251 /** 252 * Delete a partition table entry but keep the map entry intact. 253 */ 254 static void 255 partition_entry_delete(struct partition_entry *entry) 256 { 257 struct partition_map_entry *map; 258 259 assert(entry != NULL); 260 261 TAILQ_REMOVE(&partition_tbl, entry, link); 262 STAILQ_FOREACH(map, &partition_map, link) 263 if (map->entry == entry) { 264 map->entry = NULL; 265 break; 266 } 267 free(entry->id); 268 free(entry->label); 269 free(entry); 270 } 271 272 /** 273 * Find a partition table entry by name. If none is found, return NULL. 274 */ 275 static struct partition_entry * 276 partition_entry_find_by_name(const char *name) 277 { 278 struct partition_entry *entry = NULL; 279 280 TAILQ_FOREACH(entry, &partition_tbl, link) 281 if (strcmp(entry->id, name) == 0) 282 return (entry); 283 284 return (NULL); 285 } 286 287 /** 288 * Find a partition table entry by label. If none is found, return NULL. 289 */ 290 static struct partition_entry * 291 partition_entry_find_by_label(const char *name) 292 { 293 struct partition_entry *entry = NULL; 294 295 TAILQ_FOREACH(entry, &partition_tbl, link) 296 if (strcmp(entry->label, name) == 0) 297 return (entry); 298 299 return (NULL); 300 } 301 302 /** 303 * Process a chunk from libgeom(4). A chunk is either a slice or a partition. 304 * If necessary create a new partition table entry for it. In any case 305 * set the size field of the entry and set the FOUND flag. 306 */ 307 static void 308 handle_chunk(int32_t ds_index, const char *chunk_name, off_t chunk_size) 309 { 310 struct partition_entry *entry; 311 daddr_t k_size; 312 313 assert(chunk_name != NULL); 314 assert(chunk_name[0] != '\0'); 315 if (chunk_name == NULL || chunk_name[0] == '\0') 316 return; 317 318 HRDBG("ANALYZE chunk %s", chunk_name); 319 320 if ((entry = partition_entry_find_by_name(chunk_name)) == NULL) 321 if ((entry = partition_entry_create(ds_index, 322 chunk_name)) == NULL) 323 return; 324 325 entry->flags |= HR_PARTITION_FOUND; 326 327 /* actual size may overflow the SNMP type */ 328 k_size = chunk_size / 1024; 329 entry->size = (k_size > (off_t)INT_MAX ? INT_MAX : k_size); 330 } 331 332 /** 333 * Start refreshing the partition table. A call to this function will 334 * be followed by a call to handleDiskStorage() for every disk, followed 335 * by a single call to the post_refresh function. 336 */ 337 void 338 partition_tbl_pre_refresh(void) 339 { 340 struct partition_entry *entry; 341 342 /* mark each entry as missing */ 343 TAILQ_FOREACH(entry, &partition_tbl, link) 344 entry->flags &= ~HR_PARTITION_FOUND; 345 } 346 347 /** 348 * Try to find a geom(4) class by its name. Returns a pointer to that 349 * class if found NULL otherways. 350 */ 351 static struct gclass * 352 find_class(struct gmesh *mesh, const char *name) 353 { 354 struct gclass *classp; 355 356 LIST_FOREACH(classp, &mesh->lg_class, lg_class) 357 if (strcmp(classp->lg_name, name) == 0) 358 return (classp); 359 return (NULL); 360 } 361 362 /** 363 * Process all MBR-type partitions from the given disk. 364 */ 365 static void 366 get_mbr(struct gclass *classp, int32_t ds_index, const char *disk_dev_name) 367 { 368 struct ggeom *gp; 369 struct gprovider *pp; 370 struct gconfig *conf; 371 long part_type; 372 373 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { 374 /* We are only interested in partitions from this disk */ 375 if (strcmp(gp->lg_name, disk_dev_name) != 0) 376 continue; 377 378 /* 379 * Find all the non-BSD providers (these are handled in get_bsd) 380 */ 381 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { 382 LIST_FOREACH(conf, &pp->lg_config, lg_config) { 383 if (conf->lg_name == NULL || 384 conf->lg_val == NULL || 385 strcmp(conf->lg_name, "type") != 0) 386 continue; 387 388 /* 389 * We are not interested in BSD partitions 390 * (ie ad0s1 is not interesting at this point). 391 * We'll take care of them in detail (slice 392 * by slice) in get_bsd. 393 */ 394 part_type = strtol(conf->lg_val, NULL, 10); 395 if (part_type == HR_FREEBSD_PART_TYPE) 396 break; 397 HRDBG("-> MBR PROVIDER Name: %s", pp->lg_name); 398 HRDBG("Mediasize: %jd", 399 (intmax_t)pp->lg_mediasize / 1024); 400 HRDBG("Sectorsize: %u", pp->lg_sectorsize); 401 HRDBG("Mode: %s", pp->lg_mode); 402 HRDBG("CONFIG: %s: %s", 403 conf->lg_name, conf->lg_val); 404 405 handle_chunk(ds_index, pp->lg_name, 406 pp->lg_mediasize); 407 } 408 } 409 } 410 } 411 412 /** 413 * Process all BSD-type partitions from the given disk. 414 */ 415 static void 416 get_bsd_sun(struct gclass *classp, int32_t ds_index, const char *disk_dev_name) 417 { 418 struct ggeom *gp; 419 struct gprovider *pp; 420 421 LIST_FOREACH(gp, &classp->lg_geom, lg_geom) { 422 /* 423 * We are only interested in those geoms starting with 424 * the disk_dev_name passed as parameter to this function. 425 */ 426 if (strncmp(gp->lg_name, disk_dev_name, 427 strlen(disk_dev_name)) != 0) 428 continue; 429 430 LIST_FOREACH(pp, &gp->lg_provider, lg_provider) { 431 if (pp->lg_name == NULL) 432 continue; 433 handle_chunk(ds_index, pp->lg_name, pp->lg_mediasize); 434 } 435 } 436 } 437 438 /** 439 * Called from the DiskStorage table for every row. Open the GEOM(4) framework 440 * and process all the partitions in it. 441 * ds_index is the index into the DiskStorage table. 442 * This is done in two steps: for non BSD partitions the geom class "MBR" is 443 * used, for our BSD slices the "BSD" geom class. 444 */ 445 void 446 partition_tbl_handle_disk(int32_t ds_index, const char *disk_dev_name) 447 { 448 struct gmesh mesh; /* GEOM userland tree */ 449 struct gclass *classp; 450 int error; 451 452 assert(disk_dev_name != NULL); 453 assert(ds_index > 0); 454 455 HRDBG("===> getting partitions for %s <===", disk_dev_name); 456 457 /* try to construct the GEOM tree */ 458 if ((error = geom_gettree(&mesh)) != 0) { 459 syslog(LOG_WARNING, "cannot get GEOM tree: %m"); 460 return; 461 } 462 463 /* 464 * First try the GEOM "MBR" class. 465 * This is needed for non-BSD slices (aka partitions) 466 * on PC architectures. 467 */ 468 if ((classp = find_class(&mesh, "MBR")) != NULL) { 469 get_mbr(classp, ds_index, disk_dev_name); 470 } else { 471 HRDBG("cannot find \"MBR\" geom class"); 472 } 473 474 /* 475 * Get the "BSD" GEOM class. 476 * Here we'll find all the info needed about the BSD slices. 477 */ 478 if ((classp = find_class(&mesh, "BSD")) != NULL) { 479 get_bsd_sun(classp, ds_index, disk_dev_name); 480 } else { 481 /* no problem on sparc64 */ 482 HRDBG("cannot find \"BSD\" geom class"); 483 } 484 485 /* 486 * Get the "SUN" GEOM class. 487 * Here we'll find all the info needed about the SUN slices. 488 */ 489 if ((classp = find_class(&mesh, "SUN")) != NULL) { 490 get_bsd_sun(classp, ds_index, disk_dev_name); 491 } else { 492 /* no problem on i386 */ 493 HRDBG("cannot find \"SUN\" geom class"); 494 } 495 496 geom_deletetree(&mesh); 497 } 498 499 /** 500 * Finish refreshing the table. 501 */ 502 void 503 partition_tbl_post_refresh(void) 504 { 505 struct partition_entry *e, *etmp; 506 507 /* 508 * Purge items that disappeared 509 */ 510 TAILQ_FOREACH_SAFE(e, &partition_tbl, link, etmp) 511 if (!(e->flags & HR_PARTITION_FOUND)) 512 partition_entry_delete(e); 513 } 514 515 /* 516 * Finalization routine for hrPartitionTable 517 * It destroys the lists and frees any allocated heap memory 518 */ 519 void 520 fini_partition_tbl(void) 521 { 522 struct partition_map_entry *m; 523 524 while ((m = STAILQ_FIRST(&partition_map)) != NULL) { 525 STAILQ_REMOVE_HEAD(&partition_map, link); 526 if(m->entry != NULL) { 527 TAILQ_REMOVE(&partition_tbl, m->entry, link); 528 free(m->entry->id); 529 free(m->entry->label); 530 free(m->entry); 531 } 532 free(m->id); 533 free(m); 534 } 535 assert(TAILQ_EMPTY(&partition_tbl)); 536 } 537 538 /** 539 * Called from the file system code to insert the file system table index 540 * into the partition table entry. Note, that an partition table entry exists 541 * only for local file systems. 542 */ 543 void 544 handle_partition_fs_index(const char *name, int32_t fs_idx) 545 { 546 struct partition_entry *entry; 547 548 if ((entry = partition_entry_find_by_label(name)) == NULL) { 549 HRDBG("%s IS MISSING from hrPartitionTable", name); 550 return; 551 } 552 HRDBG("%s [FS index = %d] IS in hrPartitionTable", name, fs_idx); 553 entry->fs_Index = fs_idx; 554 } 555 556 /* 557 * This is the implementation for a generated (by our SNMP tool) 558 * function prototype, see hostres_tree.h 559 * It handles the SNMP operations for hrPartitionTable 560 */ 561 int 562 op_hrPartitionTable(struct snmp_context *ctx __unused, struct snmp_value *value, 563 u_int sub, u_int iidx __unused, enum snmp_op op) 564 { 565 struct partition_entry *entry; 566 567 /* 568 * Refresh the disk storage table (which refreshes the partition 569 * table) if necessary. 570 */ 571 refresh_disk_storage_tbl(0); 572 573 switch (op) { 574 575 case SNMP_OP_GETNEXT: 576 if ((entry = NEXT_OBJECT_FUNC(&partition_tbl, 577 &value->var, sub, partition_idx_cmp)) == NULL) 578 return (SNMP_ERR_NOSUCHNAME); 579 580 value->var.len = sub + 2; 581 value->var.subs[sub] = entry->index[0]; 582 value->var.subs[sub + 1] = entry->index[1]; 583 584 goto get; 585 586 case SNMP_OP_GET: 587 if ((entry = FIND_OBJECT_FUNC(&partition_tbl, 588 &value->var, sub, partition_idx_cmp)) == NULL) 589 return (SNMP_ERR_NOSUCHNAME); 590 goto get; 591 592 case SNMP_OP_SET: 593 if ((entry = FIND_OBJECT_FUNC(&partition_tbl, 594 &value->var, sub, partition_idx_cmp)) == NULL) 595 return (SNMP_ERR_NOT_WRITEABLE); 596 return (SNMP_ERR_NO_CREATION); 597 598 case SNMP_OP_ROLLBACK: 599 case SNMP_OP_COMMIT: 600 abort(); 601 } 602 abort(); 603 604 get: 605 switch (value->var.subs[sub - 1]) { 606 607 case LEAF_hrPartitionIndex: 608 value->v.integer = entry->index[1]; 609 return (SNMP_ERR_NOERROR); 610 611 case LEAF_hrPartitionLabel: 612 return (string_get(value, entry->label, -1)); 613 614 case LEAF_hrPartitionID: 615 return(string_get(value, entry->id, -1)); 616 617 case LEAF_hrPartitionSize: 618 value->v.integer = entry->size; 619 return (SNMP_ERR_NOERROR); 620 621 case LEAF_hrPartitionFSIndex: 622 value->v.integer = entry->fs_Index; 623 return (SNMP_ERR_NOERROR); 624 } 625 abort(); 626 } 627