OpenSolaris_b135/cmd/swap/swap.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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	<sys/types.h>
#include	<sys/dumpadm.h>
#include	<string.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<errno.h>
#include	<sys/param.h>
#include	<dirent.h>
#include	<sys/swap.h>
#include	<sys/sysmacros.h>
#include	<sys/mkdev.h>
#include	<sys/stat.h>
#include	<sys/statvfs.h>
#include	<sys/uadmin.h>
#include	<vm/anon.h>
#include	<fcntl.h>
#include	<locale.h>
#include	<libintl.h>
#include	<libdiskmgt.h>
#include	<sys/fs/zfs.h>

#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 <file name> [low block]\n"),
				prognamep);
	(void) fprintf(stderr, gettext("\t%s -a <file name> [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);
}