1 /* $NetBSD: ccdconfig.c,v 1.48 2008/07/20 01:20:21 lukem Exp $ */ 2 3 /*- 4 * Copyright (c) 1996, 1997 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Jason R. Thorpe. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 __COPYRIGHT("@(#) Copyright (c) 1996, 1997\ 35 The NetBSD Foundation, Inc. All rights reserved."); 36 __RCSID("$NetBSD: ccdconfig.c,v 1.48 2008/07/20 01:20:21 lukem Exp $"); 37 #endif 38 39 #include <sys/param.h> 40 #include <sys/ioctl.h> 41 #include <sys/disklabel.h> 42 #include <sys/disk.h> 43 #include <sys/stat.h> 44 #include <sys/sysctl.h> 45 #include <ctype.h> 46 #include <err.h> 47 #include <errno.h> 48 #include <fcntl.h> 49 #include <kvm.h> 50 #include <limits.h> 51 #include <nlist.h> 52 #include <stdio.h> 53 #include <stdlib.h> 54 #include <string.h> 55 #include <unistd.h> 56 #include <util.h> 57 58 #include <dev/ccdvar.h> 59 60 #include "pathnames.h" 61 62 63 static size_t lineno; 64 static gid_t egid; 65 static int verbose; 66 static const char *ccdconf = _PATH_CCDCONF; 67 68 static char *core; 69 static char *kernel; 70 71 struct flagval { 72 const char *fv_flag; 73 int fv_val; 74 } flagvaltab[] = { 75 { "CCDF_UNIFORM", CCDF_UNIFORM }, 76 { "CCDF_NOLABEL", CCDF_NOLABEL }, 77 { NULL, 0 }, 78 }; 79 80 static struct nlist nl[] = { 81 { .n_name = "_ccd_softc" }, 82 #define SYM_CCDSOFTC 0 83 { .n_name = "_numccd" }, 84 #define SYM_NUMCCD 1 85 { .n_name = "_ccd_softc_elemsize" }, 86 #define SYM_CCDSOFTCELEMSIZE 2 87 { .n_name = NULL }, 88 }; 89 90 #define CCD_CONFIG 0 /* configure a device */ 91 #define CCD_CONFIGALL 1 /* configure all devices */ 92 #define CCD_UNCONFIG 2 /* unconfigure a device */ 93 #define CCD_UNCONFIGALL 3 /* unconfigure all devices */ 94 #define CCD_DUMP 4 /* dump a ccd's configuration */ 95 96 static int checkdev(char *); 97 static int do_io(char *, u_long, struct ccd_ioctl *); 98 static int do_single(int, char **, int); 99 static int do_all(int); 100 static int dump_ccd(int, char **, int); 101 static int flags_to_val(char *); 102 static int pathtounit(char *, int *); 103 static void print_ccd_info(struct ccd_softc *, kvm_t *); 104 static char *resolve_ccdname(char *); 105 static void usage(void); 106 107 int 108 main(int argc, char *argv[]) 109 { 110 int ch, options = 0, action = CCD_CONFIG; 111 112 egid = getegid(); 113 setegid(getgid()); 114 while ((ch = getopt(argc, argv, "cCf:gM:N:uUv")) != -1) { 115 switch (ch) { 116 case 'c': 117 action = CCD_CONFIG; 118 ++options; 119 break; 120 121 case 'C': 122 action = CCD_CONFIGALL; 123 ++options; 124 break; 125 126 case 'f': 127 ccdconf = optarg; 128 break; 129 130 case 'g': 131 action = CCD_DUMP; 132 break; 133 134 case 'M': 135 core = optarg; 136 break; 137 138 case 'N': 139 kernel = optarg; 140 break; 141 142 case 'u': 143 action = CCD_UNCONFIG; 144 ++options; 145 break; 146 147 case 'U': 148 action = CCD_UNCONFIGALL; 149 ++options; 150 break; 151 152 case 'v': 153 verbose = 1; 154 break; 155 156 default: 157 usage(); 158 } 159 } 160 argc -= optind; 161 argv += optind; 162 163 if (options > 1) 164 usage(); 165 166 /* 167 * Discard setgid privileges. If not the running kernel, we toss 168 * them away totally so that bad guys can't print interesting stuff 169 * from kernel memory, otherwise switch back to kmem for the 170 * duration of the kvm_openfiles() call. 171 * 172 * We also do this if we aren't just looking... 173 */ 174 if (core != NULL || kernel != NULL || action != CCD_DUMP) 175 setgid(getgid()); 176 177 switch (action) { 178 case CCD_CONFIG: 179 case CCD_UNCONFIG: 180 exit(do_single(argc, argv, action)); 181 /* NOTREACHED */ 182 183 case CCD_CONFIGALL: 184 case CCD_UNCONFIGALL: 185 exit(do_all(action)); 186 /* NOTREACHED */ 187 188 case CCD_DUMP: 189 default: 190 exit(dump_ccd(argc, argv, action)); 191 /* NOTREACHED */ 192 } 193 /* NOTREACHED */ 194 } 195 196 static int 197 do_single(int argc, char **argv, int action) 198 { 199 struct ccd_ioctl ccio; 200 char *ccd, *cp, *cp2, **disks; 201 int noflags = 0, i, ileave, flags, j; 202 203 flags = 0; 204 memset(&ccio, 0, sizeof(ccio)); 205 206 /* 207 * If unconfiguring, all arguments are treated as ccds. 208 */ 209 if (action == CCD_UNCONFIG || action == CCD_UNCONFIGALL) { 210 for (i = 0; argc != 0; ) { 211 cp = *argv++; --argc; 212 if ((ccd = resolve_ccdname(cp)) == NULL) { 213 warnx("invalid ccd name: %s", cp); 214 i = 1; 215 continue; 216 } 217 if (do_io(ccd, CCDIOCCLR, &ccio)) 218 i = 1; 219 else 220 if (verbose) 221 printf("%s unconfigured\n", cp); 222 free(ccd); 223 } 224 return (i); 225 } 226 227 /* Make sure there are enough arguments. */ 228 if (argc < 4) { 229 if (argc == 3) { 230 /* Assume that no flags are specified. */ 231 noflags = 1; 232 } else { 233 if (action == CCD_CONFIGALL) { 234 warnx("%s: bad line: %lu", ccdconf, 235 (u_long)lineno); 236 return (1); 237 } else 238 usage(); 239 } 240 } 241 242 /* First argument is the ccd to configure. */ 243 cp = *argv++; --argc; 244 if ((ccd = resolve_ccdname(cp)) == NULL) { 245 warnx("invalid ccd name: %s", cp); 246 return (1); 247 } 248 249 /* Next argument is the interleave factor. */ 250 cp = *argv++; --argc; 251 errno = 0; /* to check for ERANGE */ 252 ileave = (int)strtol(cp, &cp2, 10); 253 if ((errno == ERANGE) || (ileave < 0) || (*cp2 != '\0')) { 254 warnx("invalid interleave factor: %s", cp); 255 free(ccd); 256 return (1); 257 } 258 259 if (noflags == 0) { 260 /* Next argument is the ccd configuration flags. */ 261 cp = *argv++; --argc; 262 if ((flags = flags_to_val(cp)) < 0) { 263 warnx("invalid flags argument: %s", cp); 264 free(ccd); 265 return (1); 266 } 267 } 268 269 /* Next is the list of disks to make the ccd from. */ 270 disks = malloc(argc * sizeof(char *)); 271 if (disks == NULL) { 272 warnx("no memory to configure ccd"); 273 free(ccd); 274 return (1); 275 } 276 for (i = 0; argc != 0; ) { 277 cp = *argv++; --argc; 278 if ((j = checkdev(cp)) == 0) 279 disks[i++] = cp; 280 else { 281 warnx("%s: %s", cp, strerror(j)); 282 free(ccd); 283 free(disks); 284 return (1); 285 } 286 } 287 288 /* Fill in the ccio. */ 289 ccio.ccio_disks = disks; 290 ccio.ccio_ndisks = i; 291 ccio.ccio_ileave = ileave; 292 ccio.ccio_flags = flags; 293 294 if (do_io(ccd, CCDIOCSET, &ccio)) { 295 free(ccd); 296 free(disks); 297 return (1); 298 } 299 300 if (verbose) { 301 printf("ccd%d: %d components ", ccio.ccio_unit, 302 ccio.ccio_ndisks); 303 for (i = 0; i < ccio.ccio_ndisks; ++i) { 304 if ((cp2 = strrchr(disks[i], '/')) != NULL) 305 ++cp2; 306 else 307 cp2 = disks[i]; 308 printf("%c%s%c", 309 i == 0 ? '(' : ' ', cp2, 310 i == ccio.ccio_ndisks - 1 ? ')' : ','); 311 } 312 printf(", %ld blocks ", (long)ccio.ccio_size); 313 if (ccio.ccio_ileave != 0) 314 printf("interleaved at %d blocks\n", ccio.ccio_ileave); 315 else 316 printf("concatenated\n"); 317 } 318 319 free(ccd); 320 free(disks); 321 return (0); 322 } 323 324 static int 325 do_all(int action) 326 { 327 FILE *f; 328 char *line, *cp, *vp, **argv, **nargv; 329 int argc, rval; 330 size_t len; 331 332 rval = 0; 333 334 (void)setegid(getgid()); 335 if ((f = fopen(ccdconf, "r")) == NULL) { 336 (void)setegid(egid); 337 warn("fopen: %s", ccdconf); 338 return (1); 339 } 340 (void)setegid(egid); 341 342 while ((line = fparseln(f, &len, &lineno, "\\\\#", FPARSELN_UNESCALL)) 343 != NULL) { 344 argc = 0; 345 argv = NULL; 346 if (len == 0) 347 goto end_of_line; 348 349 for (cp = line; cp != NULL; ) { 350 while ((vp = strsep(&cp, "\t ")) != NULL && *vp == '\0') 351 ; 352 if (vp == NULL) 353 continue; 354 355 if ((nargv = realloc(argv, 356 sizeof(char *) * (argc + 1))) == NULL) { 357 warnx("no memory to configure ccds"); 358 return (1); 359 } 360 argv = nargv; 361 argc++; 362 argv[argc - 1] = vp; 363 364 /* 365 * If our action is to unconfigure all, then pass 366 * just the first token to do_single() and ignore 367 * the rest. Since this will be encountered on 368 * our first pass through the line, the Right 369 * Thing will happen. 370 */ 371 if (action == CCD_UNCONFIGALL) { 372 if (do_single(argc, argv, action)) 373 rval = 1; 374 goto end_of_line; 375 } 376 } 377 if (argc != 0) 378 if (do_single(argc, argv, action)) 379 rval = 1; 380 381 end_of_line: 382 if (argv != NULL) 383 free(argv); 384 free(line); 385 } 386 387 (void)fclose(f); 388 return (rval); 389 } 390 391 static int 392 checkdev(char *path) 393 { 394 struct stat st; 395 396 if (stat(path, &st) != 0) 397 return (errno); 398 399 if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) 400 return (EINVAL); 401 402 return (0); 403 } 404 405 static int 406 pathtounit(char *path, int *unitp) 407 { 408 struct stat st; 409 410 if (stat(path, &st) != 0) 411 return (errno); 412 413 if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) 414 return (EINVAL); 415 416 *unitp = DISKUNIT(st.st_rdev); 417 418 return (0); 419 } 420 421 static char * 422 resolve_ccdname(char *name) 423 { 424 char c, *path; 425 size_t len; 426 int rawpart; 427 428 if (name[0] == '/' || name[0] == '.') { 429 /* Assume they gave the correct pathname. */ 430 return (strdup(name)); 431 } 432 433 len = strlen(name); 434 c = name[len - 1]; 435 436 if (isdigit((unsigned char)c)) { 437 if ((rawpart = getrawpartition()) < 0) 438 return (NULL); 439 if (asprintf(&path, "/dev/%s%c", name, 'a' + rawpart) < 0) 440 return (NULL); 441 } else 442 if (asprintf(&path, "/dev/%s", name) < 0) 443 return (NULL); 444 445 return (path); 446 } 447 448 static int 449 do_io(char *path, u_long cmd, struct ccd_ioctl *cciop) 450 { 451 int fd; 452 const char *cp; 453 454 if ((fd = open(path, O_RDWR, 0640)) < 0) { 455 warn("open: %s", path); 456 return (1); 457 } 458 459 if (ioctl(fd, cmd, cciop) < 0) { 460 switch (cmd) { 461 case CCDIOCSET: 462 cp = "CCDIOCSET"; 463 break; 464 465 case CCDIOCCLR: 466 cp = "CCDIOCCLR"; 467 break; 468 469 default: 470 cp = "unknown"; 471 } 472 warn("ioctl (%s): %s", cp, path); 473 return (1); 474 } 475 476 return (0); 477 } 478 479 #define KVM_ABORT(kd, str) { \ 480 (void)kvm_close((kd)); \ 481 warnx("%s", (str)); \ 482 warnx("%s", kvm_geterr((kd))); \ 483 return (1); \ 484 } 485 486 static int 487 dump_ccd(int argc, char **argv, int action) 488 { 489 char errbuf[_POSIX2_LINE_MAX], *ccd, *cp; 490 struct ccd_softc *cs, *kcs; 491 void *vcs; 492 size_t readsize; 493 int i, error, numccd, ccd_softc_elemsize, numconfiged = 0; 494 kvm_t *kd; 495 496 memset(errbuf, 0, sizeof(errbuf)); 497 498 vcs = NULL; 499 (void)setegid(egid); 500 if ((kd = kvm_openfiles(kernel, core, NULL, O_RDONLY, 501 errbuf)) == NULL) { 502 warnx("can't open kvm: %s", errbuf); 503 return (1); 504 } 505 (void)setgid(getgid()); 506 507 if (kvm_nlist(kd, nl)) 508 KVM_ABORT(kd, "ccd-related symbols not available"); 509 510 /* Check to see how many ccds are currently configured. */ 511 if (kvm_read(kd, nl[SYM_NUMCCD].n_value, (void *)&numccd, 512 sizeof(numccd)) != sizeof(numccd)) 513 KVM_ABORT(kd, "can't determine number of configured ccds"); 514 515 if (numccd == 0) { 516 printf("ccd driver in kernel, but is uninitialized\n"); 517 goto done; 518 } 519 520 if (kvm_read(kd, nl[SYM_CCDSOFTCELEMSIZE].n_value, 521 (void *)&ccd_softc_elemsize, sizeof(ccd_softc_elemsize)) 522 != sizeof(ccd_softc_elemsize)) 523 KVM_ABORT(kd, "can't determine size of ccd_softc"); 524 525 /* Allocate space for the kernel's configuration data. */ 526 readsize = numccd * ccd_softc_elemsize; 527 if ((vcs = malloc(readsize)) == NULL) { 528 warnx("no memory for configuration data"); 529 goto bad; 530 } 531 memset(vcs, 0, readsize); 532 533 /* 534 * Read the ccd configuration data from the kernel. 535 * The kernel's ccd_softc is larger than userland's 536 * (the former contains extra structure members at 537 * the end of the structure), so read the kernel 538 * ccd_softcs into a temporary buffer and convert 539 * into userland ccd_softcs. 540 */ 541 if (kvm_read(kd, nl[SYM_CCDSOFTC].n_value, (void *)&kcs, 542 sizeof(kcs)) != sizeof(kcs)) { 543 free(vcs); 544 KVM_ABORT(kd, "can't find pointer to configuration data"); 545 } 546 if (kvm_read(kd, (u_long)kcs, vcs, readsize) != readsize) { 547 free(vcs); 548 KVM_ABORT(kd, "can't read configuration data"); 549 } 550 551 if ((cs = calloc(numccd, sizeof(struct ccd_softc))) == NULL) { 552 warnx("no memory for configuration data"); 553 free(vcs); 554 goto bad; 555 } 556 for (i = 0; i < numccd; i++) { 557 memcpy(&cs[i], (char *)vcs + i * ccd_softc_elemsize, 558 sizeof(struct ccd_softc)); 559 } 560 free(vcs); 561 562 /* Dump ccd configuration to stdout. */ 563 if (argc == 0) { 564 for (i = 0; i < numccd; ++i) 565 if (cs[i].sc_flags & CCDF_INITED) { 566 ++numconfiged; 567 print_ccd_info(&cs[i], kd); 568 } 569 570 if (numconfiged == 0) 571 printf("# no concatenated disks configured\n"); 572 } else { 573 while (argc) { 574 cp = *argv++; --argc; 575 if ((ccd = resolve_ccdname(cp)) == NULL) { 576 warnx("invalid ccd name: %s", cp); 577 continue; 578 } 579 if ((error = pathtounit(ccd, &i)) != 0) { 580 warn("%s", ccd); 581 free(ccd); 582 continue; 583 } 584 if (i >= numccd) { 585 warnx("ccd%d not configured", i); 586 free(ccd); 587 continue; 588 } 589 if (cs[i].sc_flags & CCDF_INITED) 590 print_ccd_info(&cs[i], kd); 591 else 592 printf("# ccd%d not configured\n", i); 593 free(ccd); 594 } 595 } 596 597 free(cs); 598 599 done: 600 (void)kvm_close(kd); 601 return (0); 602 603 bad: 604 (void)kvm_close(kd); 605 return (1); 606 } 607 608 static void 609 print_ccd_info(struct ccd_softc *cs, kvm_t *kd) 610 { 611 static int header_printed = 0; 612 struct ccdcinfo *cip; 613 size_t readsize; 614 char path[MAXPATHLEN]; 615 int i; 616 617 if (header_printed == 0 && verbose) { 618 printf("# ccd\t\tileave\tflags\tcompnent devices\n"); 619 header_printed = 1; 620 } 621 622 readsize = cs->sc_nccdisks * sizeof(struct ccdcinfo); 623 if ((cip = malloc(readsize)) == NULL) { 624 warn("%s: can't allocate memory for component info", 625 cs->sc_xname); 626 return; 627 } 628 memset(cip, 0, readsize); 629 630 /* Dump out softc information. */ 631 printf("%s\t\t%d\t%d\t", cs->sc_xname, cs->sc_ileave, 632 cs->sc_flags & CCDF_USERMASK); 633 fflush(stdout); 634 635 /* Read in the component info. */ 636 if (kvm_read(kd, (u_long)cs->sc_cinfo, (void *)cip, 637 readsize) != readsize) { 638 printf("\n"); 639 warnx("can't read component info"); 640 warnx("%s", kvm_geterr(kd)); 641 goto done; 642 } 643 644 /* Read component pathname and display component info. */ 645 for (i = 0; i < cs->sc_nccdisks; ++i) { 646 if (kvm_read(kd, (u_long)cip[i].ci_path, (void *)path, 647 cip[i].ci_pathlen) != cip[i].ci_pathlen) { 648 printf("\n"); 649 warnx("can't read component pathname"); 650 warnx("%s", kvm_geterr(kd)); 651 goto done; 652 } 653 fputs(path, stdout); 654 fputc((i + 1 < cs->sc_nccdisks) ? ' ' : '\n', stdout); 655 fflush(stdout); 656 } 657 658 done: 659 free(cip); 660 } 661 662 static int 663 flags_to_val(char *flags) 664 { 665 char *cp, *tok; 666 int i, tmp, val = ~CCDF_USERMASK; 667 size_t flagslen; 668 669 /* 670 * The most common case is that of NIL flags, so check for 671 * those first. 672 */ 673 if (strcmp("none", flags) == 0 || strcmp("0x0", flags) == 0 || 674 strcmp("0", flags) == 0) 675 return (0); 676 677 flagslen = strlen(flags); 678 679 /* Check for values represented by strings. */ 680 if ((cp = strdup(flags)) == NULL) 681 err(1, "no memory to parse flags"); 682 tmp = 0; 683 for (tok = cp; (tok = strtok(tok, ",")) != NULL; tok = NULL) { 684 for (i = 0; flagvaltab[i].fv_flag != NULL; ++i) 685 if (strcmp(tok, flagvaltab[i].fv_flag) == 0) 686 break; 687 if (flagvaltab[i].fv_flag == NULL) { 688 free(cp); 689 goto bad_string; 690 } 691 tmp |= flagvaltab[i].fv_val; 692 } 693 694 /* If we get here, the string was ok. */ 695 free(cp); 696 val = tmp; 697 goto out; 698 699 bad_string: 700 701 /* Check for values represented in hex. */ 702 if (flagslen > 2 && flags[0] == '0' && flags[1] == 'x') { 703 errno = 0; /* to check for ERANGE */ 704 val = (int)strtol(&flags[2], &cp, 16); 705 if ((errno == ERANGE) || (*cp != '\0')) 706 return (-1); 707 goto out; 708 } 709 710 /* Check for values represented in decimal. */ 711 errno = 0; /* to check for ERANGE */ 712 val = (int)strtol(flags, &cp, 10); 713 if ((errno == ERANGE) || (*cp != '\0')) 714 return (-1); 715 716 out: 717 return (((val & ~CCDF_USERMASK) == 0) ? val : -1); 718 } 719 720 static void 721 usage(void) 722 { 723 const char *progname = getprogname(); 724 725 fprintf(stderr, "usage: %s [-cv] ccd ileave [flags] dev [...]\n", 726 progname); 727 fprintf(stderr, " %s -C [-v] [-f config_file]\n", progname); 728 fprintf(stderr, " %s -u [-v] ccd [...]\n", progname); 729 fprintf(stderr, " %s -U [-v] [-f config_file]\n", progname); 730 fprintf(stderr, " %s -g [-M core] [-N system] [ccd [...]]\n", 731 progname); 732 exit(1); 733 } 734