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