root/sbin/swapon/swapon.c
/*
 * Copyright (c) 1980, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * @(#) Copyright (c) 1980, 1993 The Regents of the University of California.  All rights reserved.
 * @(#)swapon.c 8.1 (Berkeley) 6/5/93
 * $FreeBSD: src/sbin/swapon/swapon.c,v 1.8.2.2 2001/07/30 10:30:11 dd Exp $
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/linker.h>
#include <sys/diskslice.h>
#include <vm/vm_param.h>

#include <err.h>
#include <errno.h>
#include <fstab.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <libutil.h>

#include <bus/cam/scsi/scsi_daio.h>

static void usage(void);
static int swap_on_off(char *name, int doingall, int trim, int ask);
static char *docrypt(char *fs_spec, int pass);
static void swaplist(int lflag, int sflag, int hflag);

static int qflag;

enum { SWAPON, SWAPOFF, SWAPCTL } orig_prog, which_prog = SWAPCTL;

int
main(int argc, char **argv)
{
        struct fstab *fsp;
        char *ptr;
        int ret;
        int ch;
        int doall, sflag, lflag, hflag, eflag, cflag, iflag;

        if ((ptr = strrchr(argv[0], '/')) == NULL)
                ptr = argv[0];
        if (strstr(ptr, "swapon"))
                which_prog = SWAPON;
        else if (strstr(ptr, "swapoff"))
                which_prog = SWAPOFF;
        orig_prog = which_prog;

        sflag = lflag = hflag = doall = eflag = cflag = iflag = 0;
        while ((ch = getopt(argc, argv, "acdeghiklmqsAEU")) != -1) {
                switch((char)ch) {
                case 'A':
                        if (which_prog == SWAPCTL) {
                                doall = 1;
                                which_prog = SWAPON;
                        } else {
                                usage();
                        }
                        break;
                case 'a':
                        if (which_prog == SWAPON || which_prog == SWAPOFF)
                                doall = 1;
                        else
                                which_prog = SWAPON;
                        break;
                case 'd':
                        if (which_prog == SWAPCTL)
                                which_prog = SWAPOFF;
                        else
                                usage();
                        break;
                case 'c':
                        cflag = 1;
                        break;
                case 'E':
                case 'e':
                        eflag = 1;
                        break;
                case 'g':
                        hflag = 'G';
                        break;
                case 'h':
                        hflag = 'H';
                        break;
                case 'i':
                        iflag = 1;
                        break;
                case 'k':
                        hflag = 'K';
                        break;
                case 'l':
                        lflag = 1;
                        break;
                case 'm':
                        hflag = 'M';
                        break;
                case 'q':
                        if (which_prog == SWAPON || which_prog == SWAPOFF)
                                qflag = 1;
                        break;
                case 's':
                        sflag = 1;
                        break;
                case 'U':
                        if (which_prog == SWAPCTL) {
                                doall = 1;
                                which_prog = SWAPOFF;
                        } else {
                                usage();
                        }
                        break;
                case '?':
                default:
                        usage();
                }
        }
        argv += optind;

        ret = 0;
        if (which_prog == SWAPON || which_prog == SWAPOFF) {
                if (doall) {
                        while ((fsp = getfsent()) != NULL) {
                                char *fs_spec;
                                int dotrim = eflag;

                                if (strcmp(fsp->fs_type, FSTAB_SW))
                                        continue;
                                if (strstr(fsp->fs_mntops, "noauto"))
                                        continue;

                                if (strstr(fsp->fs_mntops, "notrim"))
                                        dotrim = 0;
                                else if (strstr(fsp->fs_mntops, "trim"))
                                        dotrim = 1;

                                if (cflag || strstr(fsp->fs_mntops, "crypt"))
                                        fs_spec = docrypt(fsp->fs_spec, 1);
                                else
                                        fs_spec = strdup(fsp->fs_spec);
                                if (swap_on_off(fs_spec, 1, dotrim, iflag)) {
                                        ret = 1;
                                } else {
                                        if (cflag ||
                                            strstr(fsp->fs_mntops, "crypt")) {
                                                docrypt(fsp->fs_spec, 2);
                                        }
                                        if (!qflag) {
                                                printf("%s: %sing %s as swap "
                                                       "device\n",
                                                    getprogname(),
                                                    (which_prog == SWAPOFF ?
                                                            "remov" : "add"),
                                                    fs_spec);
                                        }
                                }
                                free(fs_spec);
                        }
                } else if (*argv == NULL) {
                        usage();
                }
                for (; *argv; ++argv) {
                        char *ospec = getdevpath(*argv, 0);
                        char *fs_spec;
                        if (cflag)
                                fs_spec = docrypt(ospec, 1);
                        else
                                fs_spec = strdup(ospec);
                        if (swap_on_off(fs_spec, 0, eflag, iflag)) {
                                ret = 1;
                        } else {
                                if (cflag)
                                        docrypt(ospec, 2);
                                if (!qflag) {
                                        printf("%s: %sing %s as swap device\n",
                                            getprogname(),
                                            (which_prog == SWAPOFF ?
                                                "remov" : "add"),
                                            fs_spec);
                                }
                        }
                        free(fs_spec);
                }
        } else {
                if (lflag || sflag)
                        swaplist(lflag, sflag, hflag);
                else
                        usage();
        }
        exit(ret);
}

static
char *
docrypt(char *fs_spec, int pass)
{
        char *id;
        char *res;
        char *buf;

        if ((id = strrchr(fs_spec, '/')) == NULL)
                id = fs_spec;
        else
                ++id;
        asprintf(&id, "swap-%s", id);
        asprintf(&res, "/dev/mapper/%s", id);

        switch(which_prog) {
        case SWAPOFF:
                if (pass != 2)
                        break;
                asprintf(&buf, "/sbin/cryptsetup remove %s", id);
                system(buf);
                free(buf);
                free(id);
                break;
        case SWAPON:
                if (pass != 1)
                        break;
                if (kldfind("dm_target_crypt") < 0)
                        kldload("dm_target_crypt");

                asprintf(&buf,
                         "/sbin/cryptsetup --key-file /dev/urandom "
                         "--key-size 256 create %s %s",
                         id, fs_spec);
                if (qflag == 0)
                        printf("%s\n", buf);
                system(buf);
                free(buf);
                free(id);

                /*
                 * NOTE: Don't revert to /dev/da* on error because this could
                 *       inadvertently add both /dev/da* and
                 *       /dev/mapper/swap-da*.
                 *
                 *       Allow the swapon operation to report failure or
                 *       report a duplicate.
                 */
                break;
        default:
                free(res);
                free(id);
                res = strdup(fs_spec);
                break;
        }

        if (pass == 2) {
                free (res);
                res = NULL;
        }
        return res;
}

/*
 * TRIM the device
 */
static
void
trim_volume(char * name)
{       
        struct partinfo pinfo;
        int fd,i,n;
        size_t bytes = 0,ksize;
        char *xswbuf;
        struct xswdev *xsw;


        /*
        * Determine if this device is already being used by swap without 
        * calling swapon(). 
        */
        if ((sysctlbyname("vm.swap_info_array", NULL, &bytes, NULL, 0) < 0) ||
            bytes == 0) {
                err(1, "sysctlbyname()");
        }

        xswbuf = malloc(bytes);
        if ((sysctlbyname("vm.swap_info_array", xswbuf, &bytes, NULL, 0) < 0) ||
            bytes == 0) {
                        free(xswbuf);
                        err(1, "sysctlbyname()");
        }

        ksize = ((struct xswdev *)xswbuf)->xsw_size;
        n = (int)(bytes / ksize);
        for (i = 0; i < n; ++i) {
                xsw = (void *)((char *)xswbuf + i * ksize);

                if (xsw->xsw_dev == NODEV )
                        continue;
                if(!strcmp(devname(xsw->xsw_dev, S_IFCHR),
                    name + strlen("/dev/"))) {
                        warnx("%s: device already a swap device", name);
                        exit(1);
                }
        }
        
        /*
         * Get the size and offset of this parititon/device
         */
        fd = open(name, O_RDWR);
        if (fd < 0)
                err(1, "Unable to open %s R+W", name);
        if (ioctl(fd, DIOCGPART, &pinfo) < 0) {
                printf("Cannot trim regular file\n");
                usage ();
        }
        off_t ioarg[2];
        
        /*Trim the Device*/     
        ioarg[0] = pinfo.media_offset;
        ioarg[1] = pinfo.media_size;
        printf("Trimming Device:%s, start=%jd bytes=%jd)\n",
             name, (intmax_t)ioarg[0], (intmax_t)ioarg[1]);
        if (ioctl(fd, DAIOCTRIM, ioarg) < 0) {
                printf("Device trim failed\n");
        }
        close(fd);
}

static int
swap_on_off(char *name, int doingall, int trim, int ask)
{

        if (ask && which_prog == SWAPON) {
                printf("Do you really want to use device %s as a swap device ?\n", name);
                printf("You might loose data. [Y/N]");

                int c = fgetc(stdin);
                printf("\n");
                if (c != 'y' && c != 'Y')
                        return(1);

        }
        if (which_prog == SWAPON && trim) {
                trim_volume(name);
        }
        if ((which_prog == SWAPOFF ? swapoff(name) : swapon(name)) == -1) {
                switch(errno) {
                case EBUSY:
                        if (!doingall)
                                warnx("%s: device already in use", name);
                        break;
                case EINVAL:
                        if (which_prog == SWAPON)
                                warnx("%s: NSWAPDEV limit reached", name);
                        else if (!doingall)
                                warn("%s", name);
                        break;
                default:
                        warn("%s", name);
                        break;
                }
                return(1);
        }
        return(0);
}

static void
usage(void)
{
        fprintf(stderr, "usage: %s ", getprogname());
        switch (orig_prog) {
        case SWAPON:
        case SWAPOFF:
                fprintf(stderr, "-aeiq | file ...\n");
                break;
        case SWAPCTL:
                fprintf(stderr, "[-AeghiklmsU] [-a file ... | -d file ...]\n");
                break;
        }
        exit(1);
}

static void
sizetobuf(char *buf, size_t bufsize, int hflag, long long val, int hlen,
    long blocksize)
{
        if (hflag == 'H') {
                char tmp[16];

                humanize_number(tmp, 5, (int64_t)val, "", HN_AUTOSCALE,
                    HN_B | HN_NOSPACE | HN_DECIMAL);
                snprintf(buf, bufsize, "%*s", hlen, tmp);
        } else {
                snprintf(buf, bufsize, "%*lld", hlen, val / blocksize);
        }
}

static void
swaplist(int lflag, int sflag, int hflag)
{
        size_t ksize, bytes = 0;
        char *xswbuf;
        struct xswdev *xsw;
        int hlen, pagesize;
        int i, n;
        long blocksize;
        long long total, used, tmp_total, tmp_used;
        char buf[32];

        pagesize = getpagesize();
        switch(hflag) {
        case 'G':
                blocksize = 1024 * 1024 * 1024;
                strlcpy(buf, "1GB-blocks", sizeof(buf));
                hlen = 10;
                break;
        case 'H':
                blocksize = -1;
                strlcpy(buf, "Bytes", sizeof(buf));
                hlen = 10;
                break;
        case 'K':
                blocksize = 1024;
                strlcpy(buf, "1kB-blocks", sizeof(buf));
                hlen = 10;
                break;
        case 'M':
                blocksize = 1024 * 1024;
                strlcpy(buf, "1MB-blocks", sizeof(buf));
                hlen = 10;
                break;
        default:
                getbsize(&hlen, &blocksize);
                snprintf(buf, sizeof(buf), "%ld-blocks", blocksize);
                break;
        }

        if (sysctlbyname("vm.swap_info_array", NULL, &bytes, NULL, 0) < 0)
                err(1, "sysctlbyname()");
        if (bytes == 0)
                err(1, "sysctlbyname()");

        xswbuf = malloc(bytes);
        if (sysctlbyname("vm.swap_info_array", xswbuf, &bytes, NULL, 0) < 0) {
                free(xswbuf);
                err(1, "sysctlbyname()");
        }
        if (bytes == 0) {
                free(xswbuf);
                err(1, "sysctlbyname()");
        }

        /*
         * Calculate size of xsw entry returned by kernel (it can be larger
         * than the one we have if there is a version mismatch).
         */
        ksize = ((struct xswdev *)xswbuf)->xsw_size;
        n = (int)(bytes / ksize);

        if (lflag) {
                printf("%-13s %*s %*s\n",
                    "Device:",
                    hlen, buf,
                    hlen, "Used:");
        }

        total = used = tmp_total = tmp_used = 0;
        for (i = 0; i < n; ++i) {
                xsw = (void *)((char *)xswbuf + i * ksize);

                if (xsw->xsw_nblks == 0)
                        continue;

                tmp_total = (long long)xsw->xsw_nblks * pagesize;
                tmp_used = (long long)xsw->xsw_used * pagesize;
                total += tmp_total;
                used += tmp_used;
                if (lflag) {
                        sizetobuf(buf, sizeof(buf), hflag, tmp_total, hlen,
                            blocksize);
                        if (xsw->xsw_dev == NODEV) {
                                printf("%-13s %s ", "[NFS swap]", buf);
                        } else {
                                printf("/dev/%-8s %s ",
                                    devname(xsw->xsw_dev, S_IFCHR), buf);
                        }

                        sizetobuf(buf, sizeof(buf), hflag, tmp_used, hlen,
                            blocksize);
                        printf("%s\n", buf);
                }
        }

        if (sflag) {
                sizetobuf(buf, sizeof(buf), hflag, total, hlen, blocksize);
                printf("Total:        %s ", buf);
                sizetobuf(buf, sizeof(buf), hflag, used, hlen, blocksize);
                printf("%s\n", buf);
        }
}