1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 /* 30 * University Copyright- Copyright (c) 1982, 1986, 1988 31 * The Regents of the University of California 32 * All Rights Reserved 33 * 34 * University Acknowledgment- Portions of this document are derived from 35 * software developed by the University of California, Berkeley, and its 36 * contributors. 37 */ 38 39 /* 40 * Copyright 2017 Jason King. 41 */ 42 43 /* 44 * Swap administrative interface 45 * Used to add/delete/list swap devices. 46 */ 47 48 #include <sys/types.h> 49 #include <sys/dumpadm.h> 50 #include <string.h> 51 #include <stdio.h> 52 #include <stdlib.h> 53 #include <unistd.h> 54 #include <errno.h> 55 #include <sys/param.h> 56 #include <dirent.h> 57 #include <sys/swap.h> 58 #include <sys/sysmacros.h> 59 #include <sys/mkdev.h> 60 #include <sys/stat.h> 61 #include <sys/statvfs.h> 62 #include <sys/uadmin.h> 63 #include <vm/anon.h> 64 #include <fcntl.h> 65 #include <locale.h> 66 #include <libintl.h> 67 #include <libdiskmgt.h> 68 #include <sys/fs/zfs.h> 69 #include <libcmdutils.h> 70 #include <sys/debug.h> 71 72 #define LFLAG 0x01 /* swap -l (list swap devices) */ 73 #define DFLAG 0x02 /* swap -d (delete swap device) */ 74 #define AFLAG 0x04 /* swap -a (add swap device) */ 75 #define SFLAG 0x08 /* swap -s (swap info summary) */ 76 #define P1FLAG 0x10 /* swap -1 (swapadd pass1; do not modify dump device) */ 77 #define P2FLAG 0x20 /* swap -2 (swapadd pass2; do not modify dump device) */ 78 #define HFLAG 0x40 /* swap -h (size in human readable format) */ 79 #define KFLAG 0x80 /* swap -k (size in kilobytes) */ 80 81 #define NUMBER_WIDTH 64 82 /* make sure nicenum_scale() has enough space */ 83 CTASSERT(NUMBER_WIDTH >= NN_NUMBUF_SZ); 84 typedef char numbuf_t[NUMBER_WIDTH]; 85 86 static char *prognamep; 87 88 static int add(char *, off_t, off_t, int); 89 static int delete(char *, off_t); 90 static void usage(void); 91 static int doswap(int flag); 92 static int valid(char *, off_t, off_t); 93 static int list(int flag); 94 95 int 96 main(int argc, char **argv) 97 { 98 int c, flag = 0; 99 int ret = 0; 100 int error = 0; 101 off_t s_offset = 0; 102 off_t length = 0; 103 char *pathname = NULL; 104 char *msg; 105 106 (void) setlocale(LC_ALL, ""); 107 108 #if !defined(TEXT_DOMAIN) 109 #define TEXT_DOMAIN "SYS_TEST" 110 #endif 111 (void) textdomain(TEXT_DOMAIN); 112 113 prognamep = argv[0]; 114 if (argc < 2) { 115 usage(); 116 exit(1); 117 } 118 119 while ((c = getopt(argc, argv, "khlsd:a:12")) != EOF) { 120 char *char_p; 121 switch (c) { 122 case 'l': /* list all the swap devices */ 123 flag |= LFLAG; 124 break; 125 case 's': 126 flag |= SFLAG; 127 break; 128 case 'd': 129 /* 130 * The argument for starting offset is optional. 131 * If no argument is specified, the entire swap file 132 * is added although this will fail if a non-zero 133 * starting offset was specified when added. 134 */ 135 if ((argc - optind) > 1 || flag != 0) { 136 usage(); 137 exit(1); 138 } 139 flag |= DFLAG; 140 pathname = optarg; 141 if (optind < argc) { 142 errno = 0; 143 s_offset = strtol(argv[optind++], &char_p, 10); 144 if (errno != 0 || *char_p != '\0') { 145 (void) fprintf(stderr, 146 gettext("error in [low block]\n")); 147 exit(1); 148 } 149 } 150 ret = delete(pathname, s_offset); 151 break; 152 153 case 'a': 154 /* 155 * The arguments for starting offset and number of 156 * blocks are optional. If only the starting offset 157 * is specified, all the blocks to the end of the swap 158 * file will be added. If no starting offset is 159 * specified, the entire swap file is assumed. 160 */ 161 if ((argc - optind) > 2 || 162 (flag & ~(P1FLAG | P2FLAG)) != 0) { 163 usage(); 164 exit(1); 165 } 166 if (*optarg != '/') { 167 (void) fprintf(stderr, 168 gettext("%s: path must be absolute\n"), 169 prognamep); 170 exit(1); 171 } 172 flag |= AFLAG; 173 pathname = optarg; 174 if (optind < argc) { 175 errno = 0; 176 s_offset = strtol(argv[optind++], &char_p, 10); 177 if (errno != 0 || *char_p != '\0') { 178 (void) fprintf(stderr, 179 gettext("error in [low block]\n")); 180 exit(1); 181 } 182 } 183 if (optind < argc) { 184 errno = 0; 185 length = strtol(argv[optind++], &char_p, 10); 186 if (errno != 0 || *char_p != '\0') { 187 (void) fprintf(stderr, 188 gettext("error in [nbr of blocks]\n")); 189 exit(1); 190 } 191 } 192 break; 193 case 'h': 194 flag |= HFLAG; 195 break; 196 197 case 'k': 198 flag |= KFLAG; 199 break; 200 201 case '1': 202 flag |= P1FLAG; 203 break; 204 205 case '2': 206 flag |= P2FLAG; 207 break; 208 209 case '?': 210 usage(); 211 exit(1); 212 } 213 } 214 215 if (flag & SFLAG) { 216 if (flag & ~SFLAG & ~HFLAG) { 217 /* 218 * The only option that can be used with -s is -h. 219 */ 220 usage(); 221 exit(1); 222 } 223 224 ret = doswap(flag); 225 226 } 227 228 if (flag & LFLAG) { 229 if (flag & ~KFLAG & ~HFLAG & ~LFLAG) { 230 usage(); 231 exit(1); 232 } 233 ret = list(flag); 234 } 235 236 /* 237 * do the add here. Check for in use prior to add. 238 * The values for length and offset are set above. 239 */ 240 if (flag & AFLAG) { 241 /* 242 * If device is in use for a swap device, print message 243 * and exit. 244 */ 245 if (dm_inuse(pathname, &msg, DM_WHO_SWAP, &error) || 246 error) { 247 if (error != 0) { 248 (void) fprintf(stderr, gettext("Error occurred" 249 " with device in use checking: %s\n"), 250 strerror(error)); 251 } else { 252 (void) fprintf(stderr, "%s", msg); 253 free(msg); 254 } 255 exit(1); 256 } 257 if ((ret = valid(pathname, 258 s_offset * 512, length * 512)) == 0) { 259 ret = add(pathname, s_offset, length, flag); 260 } 261 } 262 if (!(flag & ~HFLAG & ~KFLAG)) { 263 /* only -h and/or -k flag, or no flag */ 264 usage(); 265 exit(1); 266 } 267 return (ret); 268 } 269 270 271 static void 272 usage(void) 273 { 274 (void) fprintf(stderr, gettext("Usage:\t%s -l\n"), prognamep); 275 (void) fprintf(stderr, gettext("\tsub option :\n")); 276 (void) fprintf(stderr, gettext("\t\t-h : displays size in human " 277 "readable format\n")); 278 (void) fprintf(stderr, gettext("\t\t-k : displays size in KB\n")); 279 (void) fprintf(stderr, "\t%s -s\n", prognamep); 280 (void) fprintf(stderr, gettext("\tsub option :\n")); 281 (void) fprintf(stderr, gettext("\t\t-h : displays size in human " 282 "readable format rather than KB\n")); 283 (void) fprintf(stderr, gettext("\t%s -d <file name> [low block]\n"), 284 prognamep); 285 (void) fprintf(stderr, gettext("\t%s -a <file name> [low block]" 286 " [nbr of blocks]\n"), prognamep); 287 } 288 289 /* 290 * Implement: 291 * #define ctok(x) ((ctob(x))>>10) 292 * in a machine independent way. (Both assume a click > 1k) 293 */ 294 static size_t 295 ctok(pgcnt_t clicks) 296 { 297 static int factor = -1; 298 299 if (factor == -1) 300 factor = (int)(sysconf(_SC_PAGESIZE) >> 10); 301 return ((size_t)(clicks * factor)); 302 } 303 304 305 static int 306 doswap(int flag) 307 { 308 struct anoninfo ai; 309 pgcnt_t allocated, reserved, available; 310 numbuf_t numbuf; 311 312 /* 313 * max = total amount of swap space including physical memory 314 * ai.ani_max = MAX(anoninfo.ani_resv, anoninfo.ani_max) + 315 * availrmem - swapfs_minfree; 316 * ai.ani_free = amount of unallocated anonymous memory 317 * (ie. = resverved_unallocated + unreserved) 318 * ai.ani_free = anoninfo.ani_free + (availrmem - swapfs_minfree); 319 * ai.ani_resv = total amount of reserved anonymous memory 320 * ai.ani_resv = anoninfo.ani_resv; 321 * 322 * allocated = anon memory not free 323 * reserved = anon memory reserved but not allocated 324 * available = anon memory not reserved 325 */ 326 if (swapctl(SC_AINFO, &ai) == -1) { 327 perror(prognamep); 328 return (2); 329 } 330 331 allocated = ai.ani_max - ai.ani_free; 332 reserved = ai.ani_resv - allocated; 333 available = ai.ani_max - ai.ani_resv; 334 335 /* 336 * TRANSLATION_NOTE 337 * Translations (if any) of these keywords should match with 338 * translations (if any) of the swap.1M man page keywords for 339 * -s option: "allocated", "reserved", "used", "available" 340 */ 341 342 if (flag & HFLAG) { 343 int factor = (int)(sysconf(_SC_PAGESIZE)); 344 345 nicenum_scale(allocated, factor, numbuf, sizeof (numbuf), 0); 346 (void) printf(gettext("total: %s allocated + "), numbuf); 347 348 nicenum_scale(reserved, factor, numbuf, sizeof (numbuf), 0); 349 (void) printf(gettext("%s reserved = "), numbuf); 350 351 nicenum_scale(allocated + reserved, factor, numbuf, 352 sizeof (numbuf), 0); 353 (void) printf(gettext("%s used, "), numbuf); 354 355 nicenum_scale(available, factor, numbuf, sizeof (numbuf), 0); 356 (void) printf(gettext("%s available\n"), numbuf); 357 } else { 358 (void) printf(gettext("total: %luk bytes allocated + %luk" 359 " reserved = %luk used, %luk available\n"), 360 ctok(allocated), ctok(reserved), 361 ctok(reserved) + ctok(allocated), 362 ctok(available)); 363 } 364 365 return (0); 366 } 367 368 static int 369 list(int flag) 370 { 371 struct swaptable *st; 372 struct swapent *swapent; 373 int i; 374 struct stat64 statbuf; 375 char *path, *ptr; 376 char fullpath[MAXPATHLEN+1]; 377 int num; 378 numbuf_t numbuf; 379 380 if ((num = swapctl(SC_GETNSWP, NULL)) == -1) { 381 perror(prognamep); 382 return (2); 383 } 384 if (num == 0) { 385 (void) fprintf(stderr, gettext("No swap devices configured\n")); 386 return (1); 387 } 388 389 if ((st = malloc(num * sizeof (swapent_t) + sizeof (int))) 390 == NULL) { 391 (void) fprintf(stderr, 392 gettext("Malloc failed. Please try later.\n")); 393 perror(prognamep); 394 return (2); 395 } 396 if ((path = malloc(num * MAXPATHLEN)) == NULL) { 397 (void) fprintf(stderr, 398 gettext("Malloc failed. Please try later.\n")); 399 perror(prognamep); 400 free(st); 401 return (2); 402 } 403 swapent = st->swt_ent; 404 ptr = path; 405 for (i = 0; i < num; i++, swapent++) { 406 swapent->ste_path = ptr; 407 ptr += MAXPATHLEN; 408 } 409 410 st->swt_n = num; 411 if ((num = swapctl(SC_LIST, st)) == -1) { 412 perror(prognamep); 413 free(path); 414 free(st); 415 return (2); 416 } 417 418 /* 419 * TRANSLATION_NOTE 420 * Following translations for "swap -l" should account for for 421 * alignment of header and output. 422 * The first translation is for the header. If the alignment 423 * of the header changes, change the next 5 formats as needed 424 * to make alignment of output agree with alignment of the header. 425 * The next four translations are four cases for printing the 426 * 1st & 2nd fields. 427 * The next translation is for printing the 3rd, 4th & 5th fields. 428 * 429 * Translations (if any) of the following keywords should match the 430 * translations (if any) of the swap.1M man page keywords for 431 * -l option: "swapfile", "dev", "swaplo", "blocks", "free" 432 */ 433 (void) printf( 434 gettext("swapfile dev swaplo blocks free\n")); 435 436 swapent = st->swt_ent; 437 for (i = 0; i < num; i++, swapent++) { 438 if (*swapent->ste_path != '/') 439 (void) snprintf(fullpath, sizeof (fullpath), 440 "/dev/%s", swapent->ste_path); 441 else 442 (void) snprintf(fullpath, sizeof (fullpath), 443 "%s", swapent->ste_path); 444 if (stat64(fullpath, &statbuf) < 0) 445 if (*swapent->ste_path != '/') 446 (void) printf(gettext("%-20s - "), 447 swapent->ste_path); 448 else 449 (void) printf(gettext("%-20s ?,? "), 450 fullpath); 451 else { 452 if (S_ISBLK(statbuf.st_mode) || 453 S_ISCHR(statbuf.st_mode)) { 454 (void) printf(gettext("%-19s %2lu,%-2lu"), 455 fullpath, 456 major(statbuf.st_rdev), 457 minor(statbuf.st_rdev)); 458 } else { 459 (void) printf(gettext("%-20s - "), fullpath); 460 } 461 } 462 { 463 int diskblks_per_page = 464 (int)(sysconf(_SC_PAGESIZE) >> DEV_BSHIFT); 465 if (flag & HFLAG) { 466 nicenum_scale(swapent->ste_start, DEV_BSIZE, numbuf, 467 sizeof (numbuf), 0); 468 (void) printf(gettext(" %8s"), numbuf); 469 470 nicenum_scale(swapent->ste_pages, DEV_BSIZE * 471 diskblks_per_page, numbuf, sizeof (numbuf), 0); 472 (void) printf(gettext(" %8s"), numbuf); 473 474 nicenum_scale(swapent->ste_free, DEV_BSIZE * 475 diskblks_per_page, numbuf, sizeof (numbuf), 0); 476 (void) printf(gettext(" %8s"), numbuf); 477 } else if (flag & KFLAG) { 478 (void) printf(gettext(" %7luK %7luK %7luK"), 479 swapent->ste_start * DEV_BSIZE / 1024, 480 swapent->ste_pages * diskblks_per_page * 481 DEV_BSIZE / 1024, 482 swapent->ste_free * diskblks_per_page * 483 DEV_BSIZE / 1024); 484 } else { 485 (void) printf(gettext(" %8lu %8lu %8lu"), 486 swapent->ste_start, 487 swapent->ste_pages * diskblks_per_page, 488 swapent->ste_free * diskblks_per_page); 489 } 490 } 491 if (swapent->ste_flags & ST_INDEL) 492 (void) printf(" INDEL\n"); 493 else 494 (void) printf("\n"); 495 } 496 free(path); 497 free(st); 498 return (0); 499 } 500 501 static void 502 dumpadm_err(const char *warning) 503 { 504 (void) fprintf(stderr, "%s (%s):\n", warning, strerror(errno)); 505 (void) fprintf(stderr, gettext( 506 "run dumpadm(8) to verify dump configuration\n")); 507 } 508 509 static int 510 delete(char *path, off_t offset) 511 { 512 swapres_t swr; 513 int fd; 514 515 swr.sr_name = path; 516 swr.sr_start = offset; 517 518 if (swapctl(SC_REMOVE, &swr) < 0) { 519 switch (errno) { 520 case (ENOSYS): 521 (void) fprintf(stderr, gettext( 522 "%s: Invalid operation for this filesystem type\n"), 523 path); 524 break; 525 default: 526 perror(path); 527 break; 528 } 529 return (2); 530 } 531 532 /* 533 * If our swap -d succeeded, open up /dev/dump and ask what the dump 534 * device is set to. If this returns ENODEV, we just deleted the 535 * dump device, so try to change the dump device to another swap 536 * device. We do this by firing up /usr/sbin/dumpadm -ud swap. 537 */ 538 if ((fd = open("/dev/dump", O_RDONLY)) >= 0) { 539 char dumpdev[MAXPATHLEN]; 540 541 if (ioctl(fd, DIOCGETDEV, dumpdev) == -1) { 542 if (errno == ENODEV) { 543 (void) printf(gettext("%s was dump device --\n" 544 "invoking dumpadm(8) -d swap to " 545 "select new dump device\n"), path); 546 /* 547 * Close /dev/dump prior to executing dumpadm 548 * since /dev/dump mandates exclusive open. 549 */ 550 (void) close(fd); 551 552 if (system("/usr/sbin/dumpadm -ud swap") == -1) 553 dumpadm_err(gettext( 554 "Warning: failed to execute dumpadm -d swap")); 555 } else 556 dumpadm_err(gettext( 557 "Warning: failed to check dump device")); 558 } 559 (void) close(fd); 560 } else 561 dumpadm_err(gettext("Warning: failed to open /dev/dump")); 562 563 return (0); 564 } 565 566 /* 567 * swapres_t structure units are in 512-blocks 568 */ 569 static int 570 add(char *path, off_t offset, off_t cnt, int flags) 571 { 572 swapres_t swr; 573 574 int fd, have_dumpdev = 1; 575 struct statvfs fsb; 576 577 /* 578 * Before adding swap, we first check to see if we have a dump 579 * device configured. If we don't (errno == ENODEV), and if 580 * our SC_ADD is successful, then run /usr/sbin/dumpadm -ud swap 581 * to attempt to reconfigure the dump device to the new swap. 582 */ 583 if ((fd = open("/dev/dump", O_RDONLY)) >= 0) { 584 char dumpdev[MAXPATHLEN]; 585 586 if (ioctl(fd, DIOCGETDEV, dumpdev) == -1) { 587 if (errno == ENODEV) 588 have_dumpdev = 0; 589 else 590 dumpadm_err(gettext( 591 "Warning: failed to check dump device")); 592 } 593 594 (void) close(fd); 595 596 /* 597 * zvols cannot act as both a swap device and dump device. 598 */ 599 if (strncmp(dumpdev, ZVOL_FULL_DEV_DIR, 600 strlen(ZVOL_FULL_DEV_DIR)) == 0) { 601 if (strcmp(dumpdev, path) == 0) { 602 (void) fprintf(stderr, gettext("%s: zvol " 603 "cannot be used as a swap device and a " 604 "dump device\n"), path); 605 return (2); 606 } 607 } 608 609 } else if (!(flags & P1FLAG)) 610 dumpadm_err(gettext("Warning: failed to open /dev/dump")); 611 612 swr.sr_name = path; 613 swr.sr_start = offset; 614 swr.sr_length = cnt; 615 616 if (swapctl(SC_ADD, &swr) < 0) { 617 switch (errno) { 618 case (ENOSYS): 619 (void) fprintf(stderr, gettext( 620 "%s: Invalid operation for this filesystem type\n"), 621 path); 622 break; 623 case (EEXIST): 624 (void) fprintf(stderr, gettext( 625 "%s: Overlapping swap files are not allowed\n"), 626 path); 627 break; 628 default: 629 perror(path); 630 break; 631 } 632 return (2); 633 } 634 635 /* 636 * If the swapctl worked and we don't have a dump device, and /etc 637 * is part of a writeable filesystem, then run dumpadm -ud swap. 638 * If /etc (presumably part of /) is still mounted read-only, then 639 * dumpadm will fail to write its config file, so there's no point 640 * running it now. This also avoids spurious messages during boot 641 * when the first swapadd takes place, at which point / is still ro. 642 * Similarly, if swapadd invoked us with -1 or -2 (but root is 643 * writeable), we don't want to modify the dump device because 644 * /etc/init.d/savecore has yet to execute; if we run dumpadm now 645 * we would lose the user's previous setting. 646 */ 647 if (!have_dumpdev && !(flags & (P1FLAG | P2FLAG)) && 648 statvfs("/etc", &fsb) == 0 && !(fsb.f_flag & ST_RDONLY)) { 649 650 (void) printf( 651 gettext("operating system crash dump was previously " 652 "disabled --\ninvoking dumpadm(8) -d swap to select " 653 "new dump device\n")); 654 655 if (system("/usr/sbin/dumpadm -ud swap") == -1) 656 dumpadm_err(gettext( 657 "Warning: failed to execute dumpadm -d swap")); 658 } 659 660 return (0); 661 } 662 663 static int 664 valid(char *pathname, off_t offset, off_t length) 665 { 666 struct stat64 f; 667 struct statvfs64 fs; 668 off_t need; 669 670 if (stat64(pathname, &f) < 0 || statvfs64(pathname, &fs) < 0) { 671 (void) perror(pathname); 672 return (errno); 673 } 674 675 if (!((S_ISREG(f.st_mode) && (f.st_mode & S_ISVTX) == S_ISVTX) || 676 S_ISBLK(f.st_mode))) { 677 (void) fprintf(stderr, 678 gettext("\"%s\" is not valid for swapping.\n" 679 "It must be a block device or a regular file with the\n" 680 "\"save user text on execution\" bit set.\n"), 681 pathname); 682 return (EINVAL); 683 } 684 685 if (S_ISREG(f.st_mode)) { 686 if (length == 0) 687 length = (off_t)f.st_size; 688 689 /* 690 * "f.st_blocks < 8" because the first eight 691 * 512-byte sectors are always skipped 692 */ 693 694 if (f.st_size < (length - offset) || f.st_size == 0 || 695 f.st_blocks < 8 || length < 0) { 696 (void) fprintf(stderr, gettext("%s: size is invalid\n"), 697 pathname); 698 return (EINVAL); 699 } 700 701 if (offset < 0) { 702 (void) fprintf(stderr, 703 gettext("%s: low block is invalid\n"), 704 pathname); 705 return (EINVAL); 706 } 707 708 need = roundup(length, fs.f_bsize) / DEV_BSIZE; 709 710 /* 711 * "need > f.st_blocks" to account for indirect blocks 712 * Note: 713 * This can be fooled by a file large enough to 714 * contain indirect blocks that also contains holes. 715 * However, we don't know (and don't want to know) 716 * about the underlying storage implementation. 717 * But, if it doesn't have at least this many blocks, 718 * there must be a hole. 719 */ 720 721 if (need > f.st_blocks) { 722 (void) fprintf(stderr, gettext( 723 "\"%s\" may contain holes - can't swap on it.\n"), 724 pathname); 725 return (EINVAL); 726 } 727 } 728 /* 729 * else, we cannot get st_size for S_ISBLK device and 730 * no meaningful checking can be done. 731 */ 732 733 return (0); 734 } 735