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;
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
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;
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
dumpadm_err(const char * warning)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
delete(char * path,off_t offset)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
add(char * path,off_t offset,off_t cnt,int flags)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
valid(char * pathname,off_t offset,off_t length)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