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