/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * University Copyright- Copyright (c) 1982, 1986, 1988 * The Regents of the University of California * All Rights Reserved * * University Acknowledgment- Portions of this document are derived from * software developed by the University of California, Berkeley, and its * contributors. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * Swap administrative interface * Used to add/delete/list swap devices. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LFLAG 0x01 /* swap -l (list swap devices) */ #define DFLAG 0x02 /* swap -d (delete swap device) */ #define AFLAG 0x04 /* swap -a (add swap device) */ #define SFLAG 0x08 /* swap -s (swap info summary) */ #define P1FLAG 0x10 /* swap -1 (swapadd pass1; do not modify dump device) */ #define P2FLAG 0x20 /* swap -2 (swapadd pass2; do not modify dump device) */ #define HFLAG 0x40 /* swap -h (size in human readable format) */ #define KFLAG 0x80 /* swap -k (size in kilobytes) */ #define NUMBER_WIDTH 64 typedef char numbuf_t[NUMBER_WIDTH]; static char *prognamep; static int add(char *, off_t, off_t, int); static int delete(char *, off_t); static void usage(void); static int doswap(int flag); static int valid(char *, off_t, off_t); static int list(int flag); static char *number_to_scaled_string(numbuf_t buf, unsigned long long number, unsigned long long unit_from, unsigned long long scale); int main(int argc, char **argv) { int c, flag = 0; int ret; int error = 0; off_t s_offset = 0; off_t length = 0; char *pathname; char *msg; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); prognamep = argv[0]; if (argc < 2) { usage(); exit(1); } while ((c = getopt(argc, argv, "khlsd:a:12")) != EOF) { char *char_p; switch (c) { case 'l': /* list all the swap devices */ flag |= LFLAG; break; case 's': flag |= SFLAG; break; case 'd': /* * The argument for starting offset is optional. * If no argument is specified, the entire swap file * is added although this will fail if a non-zero * starting offset was specified when added. */ if ((argc - optind) > 1 || flag != 0) { usage(); exit(1); } flag |= DFLAG; pathname = optarg; if (optind < argc) { errno = 0; s_offset = strtol(argv[optind++], &char_p, 10); if (errno != 0 || *char_p != '\0') { (void) fprintf(stderr, gettext("error in [low block]\n")); exit(1); } } ret = delete(pathname, s_offset); break; case 'a': /* * The arguments for starting offset and number of * blocks are optional. If only the starting offset * is specified, all the blocks to the end of the swap * file will be added. If no starting offset is * specified, the entire swap file is assumed. */ if ((argc - optind) > 2 || (flag & ~(P1FLAG | P2FLAG)) != 0) { usage(); exit(1); } if (*optarg != '/') { (void) fprintf(stderr, gettext("%s: path must be absolute\n"), prognamep); exit(1); } flag |= AFLAG; pathname = optarg; if (optind < argc) { errno = 0; s_offset = strtol(argv[optind++], &char_p, 10); if (errno != 0 || *char_p != '\0') { (void) fprintf(stderr, gettext("error in [low block]\n")); exit(1); } } if (optind < argc) { errno = 0; length = strtol(argv[optind++], &char_p, 10); if (errno != 0 || *char_p != '\0') { (void) fprintf(stderr, gettext("error in [nbr of blocks]\n")); exit(1); } } break; case 'h': flag |= HFLAG; break; case 'k': flag |= KFLAG; break; case '1': flag |= P1FLAG; break; case '2': flag |= P2FLAG; break; case '?': usage(); exit(1); } } if (flag & SFLAG) { if (flag & ~SFLAG & ~HFLAG) { /* * The only option that can be used with -s is -h. */ usage(); exit(1); } ret = doswap(flag); } if (flag & LFLAG) { if (flag & ~KFLAG & ~HFLAG & ~LFLAG) { usage(); exit(1); } ret = list(flag); } /* * do the add here. Check for in use prior to add. * The values for length and offset are set above. */ if (flag & AFLAG) { /* * If device is in use for a swap device, print message * and exit. */ if (dm_inuse(pathname, &msg, DM_WHO_SWAP, &error) || error) { if (error != 0) { (void) fprintf(stderr, gettext("Error occurred" " with device in use checking: %s\n"), strerror(error)); } else { (void) fprintf(stderr, "%s", msg); free(msg); exit(1); } } if ((ret = valid(pathname, s_offset * 512, length * 512)) == 0) { ret = add(pathname, s_offset, length, flag); } } if (!(flag & ~HFLAG & ~KFLAG)) { /* only -h and/or -k flag, or no flag */ usage(); exit(1); } return (ret); } static void usage(void) { (void) fprintf(stderr, gettext("Usage:\t%s -l\n"), prognamep); (void) fprintf(stderr, gettext("\tsub option :\n")); (void) fprintf(stderr, gettext("\t\t-h : displays size in human " "readable format\n")); (void) fprintf(stderr, gettext("\t\t-k : displays size in KB\n")); (void) fprintf(stderr, "\t%s -s\n", prognamep); (void) fprintf(stderr, gettext("\tsub option :\n")); (void) fprintf(stderr, gettext("\t\t-h : displays size in human " "readable format rather than KB\n")); (void) fprintf(stderr, gettext("\t%s -d [low block]\n"), prognamep); (void) fprintf(stderr, gettext("\t%s -a [low block]" " [nbr of blocks]\n"), prognamep); } /* * Implement: * #define ctok(x) ((ctob(x))>>10) * in a machine independent way. (Both assume a click > 1k) */ static size_t ctok(pgcnt_t clicks) { static int factor = -1; if (factor == -1) factor = (int)(sysconf(_SC_PAGESIZE) >> 10); return ((size_t)(clicks * factor)); } static int doswap(int flag) { struct anoninfo ai; pgcnt_t allocated, reserved, available; numbuf_t numbuf; unsigned long long scale = 1024L; /* * max = total amount of swap space including physical memory * ai.ani_max = MAX(anoninfo.ani_resv, anoninfo.ani_max) + * availrmem - swapfs_minfree; * ai.ani_free = amount of unallocated anonymous memory * (ie. = resverved_unallocated + unreserved) * ai.ani_free = anoninfo.ani_free + (availrmem - swapfs_minfree); * ai.ani_resv = total amount of reserved anonymous memory * ai.ani_resv = anoninfo.ani_resv; * * allocated = anon memory not free * reserved = anon memory reserved but not allocated * available = anon memory not reserved */ if (swapctl(SC_AINFO, &ai) == -1) { perror(prognamep); return (2); } allocated = ai.ani_max - ai.ani_free; reserved = ai.ani_resv - allocated; available = ai.ani_max - ai.ani_resv; /* * TRANSLATION_NOTE * Translations (if any) of these keywords should match with * translations (if any) of the swap.1M man page keywords for * -s option: "allocated", "reserved", "used", "available" */ if (flag & HFLAG) { int factor = (int)(sysconf(_SC_PAGESIZE)); (void) printf(gettext("total: %s allocated + "), number_to_scaled_string(numbuf, allocated, factor, scale)); (void) printf(gettext("%s reserved = "), number_to_scaled_string(numbuf, reserved, factor, scale)); (void) printf(gettext("%s used, "), number_to_scaled_string(numbuf, allocated + reserved, factor, scale)); (void) printf(gettext("%s available\n"), number_to_scaled_string(numbuf, available, factor, scale)); } else { (void) printf(gettext("total: %luk bytes allocated + %luk" " reserved = %luk used, %luk available\n"), ctok(allocated), ctok(reserved), ctok(reserved) + ctok(allocated), ctok(available)); } return (0); } static int list(int flag) { struct swaptable *st; struct swapent *swapent; int i; struct stat64 statbuf; char *path; char fullpath[MAXPATHLEN+1]; int num; numbuf_t numbuf; unsigned long long scale = 1024L; if ((num = swapctl(SC_GETNSWP, NULL)) == -1) { perror(prognamep); return (2); } if (num == 0) { (void) fprintf(stderr, gettext("No swap devices configured\n")); return (1); } if ((st = malloc(num * sizeof (swapent_t) + sizeof (int))) == NULL) { (void) fprintf(stderr, gettext("Malloc failed. Please try later.\n")); perror(prognamep); return (2); } if ((path = malloc(num * MAXPATHLEN)) == NULL) { (void) fprintf(stderr, gettext("Malloc failed. Please try later.\n")); perror(prognamep); return (2); } swapent = st->swt_ent; for (i = 0; i < num; i++, swapent++) { swapent->ste_path = path; path += MAXPATHLEN; } st->swt_n = num; if ((num = swapctl(SC_LIST, st)) == -1) { perror(prognamep); return (2); } /* * TRANSLATION_NOTE * Following translations for "swap -l" should account for for * alignment of header and output. * The first translation is for the header. If the alignment * of the header changes, change the next 5 formats as needed * to make alignment of output agree with alignment of the header. * The next four translations are four cases for printing the * 1st & 2nd fields. * The next translation is for printing the 3rd, 4th & 5th fields. * * Translations (if any) of the following keywords should match the * translations (if any) of the swap.1M man page keywords for * -l option: "swapfile", "dev", "swaplo", "blocks", "free" */ (void) printf( gettext("swapfile dev swaplo blocks free\n")); swapent = st->swt_ent; for (i = 0; i < num; i++, swapent++) { if (*swapent->ste_path != '/') (void) snprintf(fullpath, sizeof (fullpath), "/dev/%s", swapent->ste_path); else (void) snprintf(fullpath, sizeof (fullpath), "%s", swapent->ste_path); if (stat64(fullpath, &statbuf) < 0) if (*swapent->ste_path != '/') (void) printf(gettext("%-20s - "), swapent->ste_path); else (void) printf(gettext("%-20s ?,? "), fullpath); else { if (S_ISBLK(statbuf.st_mode) || S_ISCHR(statbuf.st_mode)) { (void) printf(gettext("%-19s %2lu,%-2lu"), fullpath, major(statbuf.st_rdev), minor(statbuf.st_rdev)); } else { (void) printf(gettext("%-20s - "), fullpath); } } { int diskblks_per_page = (int)(sysconf(_SC_PAGESIZE) >> DEV_BSHIFT); if (flag & HFLAG) { (void) printf(gettext(" %8s"), number_to_scaled_string(numbuf, swapent->ste_start, DEV_BSIZE, scale)); (void) printf(gettext(" %8s"), number_to_scaled_string(numbuf, swapent->ste_pages * diskblks_per_page, DEV_BSIZE, scale)); (void) printf(gettext(" %8s"), number_to_scaled_string(numbuf, swapent->ste_free * diskblks_per_page, DEV_BSIZE, scale)); } else if (flag & KFLAG) { (void) printf(gettext(" %7luK %7luK %7luK"), swapent->ste_start * DEV_BSIZE / 1024, swapent->ste_pages * diskblks_per_page * DEV_BSIZE / 1024, swapent->ste_free * diskblks_per_page * DEV_BSIZE / 1024); } else { (void) printf(gettext(" %8lu %8lu %8lu"), swapent->ste_start, swapent->ste_pages * diskblks_per_page, swapent->ste_free * diskblks_per_page); } } if (swapent->ste_flags & ST_INDEL) (void) printf(" INDEL\n"); else (void) printf("\n"); } return (0); } /* Copied from du.c */ static char * number_to_scaled_string( numbuf_t buf, /* put the result here */ unsigned long long number, /* convert this number */ unsigned long long unit_from, /* number of byes per input unit */ unsigned long long scale) /* 1024 (-h) or 1000 (-H) */ { unsigned long long save = 0; char *M = "KMGTPE"; /* Measurement: kilo, mega, giga, tera, peta, exa */ char *uom = M; /* unit of measurement, initially 'K' (=M[0]) */ if ((long long)number == (long long) -1) { (void) strcpy(buf, "-1"); return (buf); } /* * Convert number from unit_from to given scale (1024 or 1000) * This means multiply number with unit_from and divide by scale. * if number is large enough, we first divide and then multiply * to avoid an overflow (large enough here means 100 (rather arbitrary * value) times scale in order to reduce rounding errors) * otherwise, we first multiply and then divide to avoid an underflow. */ if (number >= 100L * scale) { number = number / scale; number = number * unit_from; } else { number = number * unit_from; number = number / scale; } /* * Now we have number as a count of scale units. * Stop scaling when we reached exa bytes, then something is * probably wrong with our number. */ while ((number >= scale) && (*uom != 'E')) { uom++; /* Next unit of measurement */ save = number; number = (number + (scale / 2)) / scale; } /* Check if we should output a decimal place after the point */ if (save && ((save / scale) < 10)) { /* sprintf() will round for us */ float fnum = (float)save / scale; (void) sprintf(buf, "%.1f%c", fnum, *uom); } else { (void) sprintf(buf, "%llu%c", number, *uom); } return (buf); } static void dumpadm_err(const char *warning) { (void) fprintf(stderr, "%s (%s):\n", warning, strerror(errno)); (void) fprintf(stderr, gettext( "run dumpadm(1M) to verify dump configuration\n")); } static int delete(char *path, off_t offset) { swapres_t swr; int fd; swr.sr_name = path; swr.sr_start = offset; if (swapctl(SC_REMOVE, &swr) < 0) { switch (errno) { case (ENOSYS): (void) fprintf(stderr, gettext( "%s: Invalid operation for this filesystem type\n"), path); break; default: perror(path); break; } return (2); } /* * If our swap -d succeeded, open up /dev/dump and ask what the dump * device is set to. If this returns ENODEV, we just deleted the * dump device, so try to change the dump device to another swap * device. We do this by firing up /usr/sbin/dumpadm -ud swap. */ if ((fd = open("/dev/dump", O_RDONLY)) >= 0) { char dumpdev[MAXPATHLEN]; if (ioctl(fd, DIOCGETDEV, dumpdev) == -1) { if (errno == ENODEV) { (void) printf(gettext("%s was dump device --\n" "invoking dumpadm(1M) -d swap to " "select new dump device\n"), path); /* * Close /dev/dump prior to executing dumpadm * since /dev/dump mandates exclusive open. */ (void) close(fd); if (system("/usr/sbin/dumpadm -ud swap") == -1) dumpadm_err(gettext( "Warning: failed to execute dumpadm -d swap")); } else dumpadm_err(gettext( "Warning: failed to check dump device")); } (void) close(fd); } else dumpadm_err(gettext("Warning: failed to open /dev/dump")); return (0); } /* * swapres_t structure units are in 512-blocks */ static int add(char *path, off_t offset, off_t cnt, int flags) { swapres_t swr; int fd, have_dumpdev = 1; struct statvfs fsb; /* * Before adding swap, we first check to see if we have a dump * device configured. If we don't (errno == ENODEV), and if * our SC_ADD is successful, then run /usr/sbin/dumpadm -ud swap * to attempt to reconfigure the dump device to the new swap. */ if ((fd = open("/dev/dump", O_RDONLY)) >= 0) { char dumpdev[MAXPATHLEN]; if (ioctl(fd, DIOCGETDEV, dumpdev) == -1) { if (errno == ENODEV) have_dumpdev = 0; else dumpadm_err(gettext( "Warning: failed to check dump device")); } (void) close(fd); /* * zvols cannot act as both a swap device and dump device. */ if (strncmp(dumpdev, ZVOL_FULL_DEV_DIR, strlen(ZVOL_FULL_DEV_DIR)) == 0) { if (strcmp(dumpdev, path) == 0) { (void) fprintf(stderr, gettext("%s: zvol " "cannot be used as a swap device and a " "dump device\n"), path); return (2); } } } else if (!(flags & P1FLAG)) dumpadm_err(gettext("Warning: failed to open /dev/dump")); swr.sr_name = path; swr.sr_start = offset; swr.sr_length = cnt; if (swapctl(SC_ADD, &swr) < 0) { switch (errno) { case (ENOSYS): (void) fprintf(stderr, gettext( "%s: Invalid operation for this filesystem type\n"), path); break; case (EEXIST): (void) fprintf(stderr, gettext( "%s: Overlapping swap files are not allowed\n"), path); break; default: perror(path); break; } return (2); } /* * If the swapctl worked and we don't have a dump device, and /etc * is part of a writeable filesystem, then run dumpadm -ud swap. * If /etc (presumably part of /) is still mounted read-only, then * dumpadm will fail to write its config file, so there's no point * running it now. This also avoids spurious messages during boot * when the first swapadd takes place, at which point / is still ro. * Similarly, if swapadd invoked us with -1 or -2 (but root is * writeable), we don't want to modify the dump device because * /etc/init.d/savecore has yet to execute; if we run dumpadm now * we would lose the user's previous setting. */ if (!have_dumpdev && !(flags & (P1FLAG | P2FLAG)) && statvfs("/etc", &fsb) == 0 && !(fsb.f_flag & ST_RDONLY)) { (void) printf( gettext("operating system crash dump was previously " "disabled --\ninvoking dumpadm(1M) -d swap to select " "new dump device\n")); if (system("/usr/sbin/dumpadm -ud swap") == -1) dumpadm_err(gettext( "Warning: failed to execute dumpadm -d swap")); } return (0); } static int valid(char *pathname, off_t offset, off_t length) { struct stat64 f; struct statvfs64 fs; off_t need; if (stat64(pathname, &f) < 0 || statvfs64(pathname, &fs) < 0) { (void) perror(pathname); return (errno); } if (!((S_ISREG(f.st_mode) && (f.st_mode & S_ISVTX) == S_ISVTX) || S_ISBLK(f.st_mode))) { (void) fprintf(stderr, gettext("\"%s\" is not valid for swapping.\n" "It must be a block device or a regular file with the\n" "\"save user text on execution\" bit set.\n"), pathname); return (EINVAL); } if (S_ISREG(f.st_mode)) { if (length == 0) length = (off_t)f.st_size; /* * "f.st_blocks < 8" because the first eight * 512-byte sectors are always skipped */ if (f.st_size < (length - offset) || f.st_size == 0 || f.st_size > MAXOFF_T || f.st_blocks < 8 || length < 0) { (void) fprintf(stderr, gettext("%s: size is invalid\n"), pathname); return (EINVAL); } if (offset < 0) { (void) fprintf(stderr, gettext("%s: low block is invalid\n"), pathname); return (EINVAL); } need = roundup(length, fs.f_bsize) / DEV_BSIZE; /* * "need > f.st_blocks" to account for indirect blocks * Note: * This can be fooled by a file large enough to * contain indirect blocks that also contains holes. * However, we don't know (and don't want to know) * about the underlying storage implementation. * But, if it doesn't have at least this many blocks, * there must be a hole. */ if (need > f.st_blocks) { (void) fprintf(stderr, gettext( "\"%s\" may contain holes - can't swap on it.\n"), pathname); return (EINVAL); } } /* * else, we cannot get st_size for S_ISBLK device and * no meaningful checking can be done. */ return (0); }