xref: /illumos-gate/usr/src/cmd/swap/swap.c (revision 5f01ecd8941eadb64bc15b1a02c468604c1a503e)
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
main(int argc,char ** argv)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
usage(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
ctok(pgcnt_t clicks)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
doswap(int flag)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
list(int flag)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
dumpadm_err(const char * warning)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
delete(char * path,off_t offset)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
add(char * path,off_t offset,off_t cnt,int flags)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
valid(char * pathname,off_t offset,off_t length)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