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