xref: /illumos-gate/usr/src/cmd/swap/swap.c (revision 915894ef19890baaed00080f85f6b69e225cda98)
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
96 main(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 
271 static 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
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
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
369 list(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 
495 static void
496 dumpadm_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 
503 static int
504 delete(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  */
563 static int
564 add(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 
657 static int
658 valid(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