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 */
83CTASSERT(NUMBER_WIDTH >= NN_NUMBUF_SZ);
84typedef char numbuf_t[NUMBER_WIDTH];
85
86static char *prognamep;
87
88static int add(char *, off_t, off_t, int);
89static int delete(char *, off_t);
90static void usage(void);
91static int doswap(int flag);
92static int valid(char *, off_t, off_t);
93static int list(int flag);
94
95int
96main(int argc, char **argv)
97{
98	int c, flag = 0;
99	int ret;
100	int error = 0;
101	off_t s_offset = 0;
102	off_t length = 0;
103	char *pathname;
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				exit(1);
255			}
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
271static void
272usage(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 */
294static size_t
295ctok(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
305static int
306doswap(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
368static int
369list(int flag)
370{
371	struct swaptable 	*st;
372	struct swapent	*swapent;
373	int	i;
374	struct stat64 statbuf;
375	char		*path;
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		return (2);
401	}
402	swapent = st->swt_ent;
403	for (i = 0; i < num; i++, swapent++) {
404		swapent->ste_path = path;
405		path += MAXPATHLEN;
406	}
407
408	st->swt_n = num;
409	if ((num = swapctl(SC_LIST, st)) == -1) {
410		perror(prognamep);
411		return (2);
412	}
413
414	/*
415	 * TRANSLATION_NOTE
416	 * Following translations for "swap -l" should account for for
417	 * alignment of header and output.
418	 * The first translation is for the header.  If the alignment
419	 *	of the header changes, change the next 5 formats as needed
420	 *	to make alignment of output agree with alignment of the header.
421	 * The next four translations are four cases for printing the
422	 * 	1st & 2nd fields.
423	 * The next translation is for printing the 3rd, 4th & 5th fields.
424	 *
425	 * Translations (if any) of the following keywords should match the
426	 * translations (if any) of the swap.1M man page keywords for
427	 * -l option:  "swapfile", "dev", "swaplo", "blocks", "free"
428	 */
429	(void) printf(
430	    gettext("swapfile             dev    swaplo   blocks     free\n"));
431
432	swapent = st->swt_ent;
433	for (i = 0; i < num; i++, swapent++) {
434		if (*swapent->ste_path != '/')
435			(void) snprintf(fullpath, sizeof (fullpath),
436			    "/dev/%s", swapent->ste_path);
437		else
438			(void) snprintf(fullpath, sizeof (fullpath),
439			    "%s", swapent->ste_path);
440		if (stat64(fullpath, &statbuf) < 0)
441			if (*swapent->ste_path != '/')
442				(void) printf(gettext("%-20s  -  "),
443				    swapent->ste_path);
444			else
445				(void) printf(gettext("%-20s ?,? "),
446				    fullpath);
447		else {
448			if (S_ISBLK(statbuf.st_mode) ||
449			    S_ISCHR(statbuf.st_mode)) {
450				(void) printf(gettext("%-19s %2lu,%-2lu"),
451				    fullpath,
452				    major(statbuf.st_rdev),
453				    minor(statbuf.st_rdev));
454			} else {
455				(void) printf(gettext("%-20s  -  "), fullpath);
456			}
457		}
458		{
459		int diskblks_per_page =
460		    (int)(sysconf(_SC_PAGESIZE) >> DEV_BSHIFT);
461		if (flag & HFLAG) {
462			nicenum_scale(swapent->ste_start, DEV_BSIZE, numbuf,
463			    sizeof (numbuf), 0);
464			(void) printf(gettext(" %8s"), numbuf);
465
466			nicenum_scale(swapent->ste_pages, DEV_BSIZE *
467			    diskblks_per_page, numbuf, sizeof (numbuf), 0);
468			(void) printf(gettext(" %8s"), numbuf);
469
470			nicenum_scale(swapent->ste_free, DEV_BSIZE *
471			    diskblks_per_page, numbuf, sizeof (numbuf), 0);
472			(void) printf(gettext(" %8s"), numbuf);
473		} else if (flag & KFLAG) {
474			(void) printf(gettext(" %7luK %7luK %7luK"),
475			    swapent->ste_start * DEV_BSIZE / 1024,
476			    swapent->ste_pages * diskblks_per_page *
477			    DEV_BSIZE / 1024,
478			    swapent->ste_free * diskblks_per_page *
479			    DEV_BSIZE / 1024);
480		} else {
481			(void) printf(gettext(" %8lu %8lu %8lu"),
482			    swapent->ste_start,
483			    swapent->ste_pages * diskblks_per_page,
484			    swapent->ste_free * diskblks_per_page);
485		}
486		}
487		if (swapent->ste_flags & ST_INDEL)
488			(void) printf(" INDEL\n");
489		else
490			(void) printf("\n");
491	}
492	return (0);
493}
494
495static void
496dumpadm_err(const char *warning)
497{
498	(void) fprintf(stderr, "%s (%s):\n", warning, strerror(errno));
499	(void) fprintf(stderr, gettext(
500	    "run dumpadm(1M) to verify dump configuration\n"));
501}
502
503static int
504delete(char *path, off_t offset)
505{
506	swapres_t swr;
507	int fd;
508
509	swr.sr_name = path;
510	swr.sr_start = offset;
511
512	if (swapctl(SC_REMOVE, &swr) < 0) {
513		switch (errno) {
514		case (ENOSYS):
515			(void) fprintf(stderr, gettext(
516			    "%s: Invalid operation for this filesystem type\n"),
517			    path);
518			break;
519		default:
520			perror(path);
521			break;
522		}
523		return (2);
524	}
525
526	/*
527	 * If our swap -d succeeded, open up /dev/dump and ask what the dump
528	 * device is set to.  If this returns ENODEV, we just deleted the
529	 * dump device, so try to change the dump device to another swap
530	 * device.  We do this by firing up /usr/sbin/dumpadm -ud swap.
531	 */
532	if ((fd = open("/dev/dump", O_RDONLY)) >= 0) {
533		char dumpdev[MAXPATHLEN];
534
535		if (ioctl(fd, DIOCGETDEV, dumpdev) == -1) {
536			if (errno == ENODEV) {
537				(void) printf(gettext("%s was dump device --\n"
538				    "invoking dumpadm(1M) -d swap to "
539				    "select new dump device\n"), path);
540				/*
541				 * Close /dev/dump prior to executing dumpadm
542				 * since /dev/dump mandates exclusive open.
543				 */
544				(void) close(fd);
545
546				if (system("/usr/sbin/dumpadm -ud swap") == -1)
547					dumpadm_err(gettext(
548				"Warning: failed to execute dumpadm -d swap"));
549			} else
550				dumpadm_err(gettext(
551				"Warning: failed to check dump device"));
552		}
553		(void) close(fd);
554	} else
555		dumpadm_err(gettext("Warning: failed to open /dev/dump"));
556
557	return (0);
558}
559
560/*
561 * swapres_t structure units are in 512-blocks
562 */
563static int
564add(char *path, off_t offset, off_t cnt, int flags)
565{
566	swapres_t swr;
567
568	int fd, have_dumpdev = 1;
569	struct statvfs fsb;
570
571	/*
572	 * Before adding swap, we first check to see if we have a dump
573	 * device configured.  If we don't (errno == ENODEV), and if
574	 * our SC_ADD is successful, then run /usr/sbin/dumpadm -ud swap
575	 * to attempt to reconfigure the dump device to the new swap.
576	 */
577	if ((fd = open("/dev/dump", O_RDONLY)) >= 0) {
578		char dumpdev[MAXPATHLEN];
579
580		if (ioctl(fd, DIOCGETDEV, dumpdev) == -1) {
581			if (errno == ENODEV)
582				have_dumpdev = 0;
583			else
584				dumpadm_err(gettext(
585				    "Warning: failed to check dump device"));
586		}
587
588		(void) close(fd);
589
590		/*
591		 * zvols cannot act as both a swap device and dump device.
592		 */
593		if (strncmp(dumpdev, ZVOL_FULL_DEV_DIR,
594		    strlen(ZVOL_FULL_DEV_DIR)) == 0) {
595			if (strcmp(dumpdev, path) == 0) {
596				(void) fprintf(stderr, gettext("%s: zvol "
597				    "cannot be used as a swap device and a "
598				    "dump device\n"), path);
599				return (2);
600			}
601		}
602
603	} else if (!(flags & P1FLAG))
604		dumpadm_err(gettext("Warning: failed to open /dev/dump"));
605
606	swr.sr_name = path;
607	swr.sr_start = offset;
608	swr.sr_length = cnt;
609
610	if (swapctl(SC_ADD, &swr) < 0) {
611		switch (errno) {
612		case (ENOSYS):
613			(void) fprintf(stderr, gettext(
614			    "%s: Invalid operation for this filesystem type\n"),
615			    path);
616			break;
617		case (EEXIST):
618			(void) fprintf(stderr, gettext(
619			    "%s: Overlapping swap files are not allowed\n"),
620			    path);
621			break;
622		default:
623			perror(path);
624			break;
625		}
626		return (2);
627	}
628
629	/*
630	 * If the swapctl worked and we don't have a dump device, and /etc
631	 * is part of a writeable filesystem, then run dumpadm -ud swap.
632	 * If /etc (presumably part of /) is still mounted read-only, then
633	 * dumpadm will fail to write its config file, so there's no point
634	 * running it now.  This also avoids spurious messages during boot
635	 * when the first swapadd takes place, at which point / is still ro.
636	 * Similarly, if swapadd invoked us with -1 or -2 (but root is
637	 * writeable), we don't want to modify the dump device because
638	 * /etc/init.d/savecore has yet to execute; if we run dumpadm now
639	 * we would lose the user's previous setting.
640	 */
641	if (!have_dumpdev && !(flags & (P1FLAG | P2FLAG)) &&
642	    statvfs("/etc", &fsb) == 0 && !(fsb.f_flag & ST_RDONLY)) {
643
644		(void) printf(
645		    gettext("operating system crash dump was previously "
646		    "disabled --\ninvoking dumpadm(1M) -d swap to select "
647		    "new dump device\n"));
648
649		if (system("/usr/sbin/dumpadm -ud swap") == -1)
650			dumpadm_err(gettext(
651			    "Warning: failed to execute dumpadm -d swap"));
652	}
653
654	return (0);
655}
656
657static int
658valid(char *pathname, off_t offset, off_t length)
659{
660	struct stat64		f;
661	struct statvfs64	fs;
662	off_t		need;
663
664	if (stat64(pathname, &f) < 0 || statvfs64(pathname,  &fs) < 0) {
665		(void) perror(pathname);
666		return (errno);
667	}
668
669	if (!((S_ISREG(f.st_mode) && (f.st_mode & S_ISVTX) == S_ISVTX) ||
670	    S_ISBLK(f.st_mode))) {
671		(void) fprintf(stderr,
672		    gettext("\"%s\" is not valid for swapping.\n"
673		    "It must be a block device or a regular file with the\n"
674		    "\"save user text on execution\" bit set.\n"),
675		    pathname);
676		return (EINVAL);
677	}
678
679	if (S_ISREG(f.st_mode)) {
680		if (length == 0)
681			length = (off_t)f.st_size;
682
683		/*
684		 * "f.st_blocks < 8" because the first eight
685		 * 512-byte sectors are always skipped
686		 */
687
688		if (f.st_size < (length - offset) || f.st_size == 0 ||
689		    f.st_size > MAXOFF_T || f.st_blocks < 8 || length < 0) {
690			(void) fprintf(stderr, gettext("%s: size is invalid\n"),
691			    pathname);
692			return (EINVAL);
693		}
694
695		if (offset < 0) {
696			(void) fprintf(stderr,
697			    gettext("%s: low block is invalid\n"),
698			    pathname);
699			return (EINVAL);
700		}
701
702		need = roundup(length, fs.f_bsize) / DEV_BSIZE;
703
704		/*
705		 * "need > f.st_blocks" to account for indirect blocks
706		 * Note:
707		 *  This can be fooled by a file large enough to
708		 *  contain indirect blocks that also contains holes.
709		 *  However, we don't know (and don't want to know)
710		 *  about the underlying storage implementation.
711		 *  But, if it doesn't have at least this many blocks,
712		 *  there must be a hole.
713		 */
714
715		if (need > f.st_blocks) {
716			(void) fprintf(stderr, gettext(
717			    "\"%s\" may contain holes - can't swap on it.\n"),
718			    pathname);
719			return (EINVAL);
720		}
721	}
722	/*
723	 * else, we cannot get st_size for S_ISBLK device and
724	 * no meaningful checking can be done.
725	 */
726
727	return (0);
728}
729