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