/*      $NetBSD: vnconfig.c,v 1.48 2018/10/07 20:30:50 wiz Exp $        */

/*-
* Copyright (c) 1997 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Jason R. Thorpe.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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) 1993 University of Utah.
* Copyright (c) 1990, 1993
*      The Regents of the University of California.  All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* the Systems Programming Group of the University of Utah Computer
* Science Department.
*
* 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.
*
* from: Utah $Hdr: vnconfig.c 1.1 93/12/15$
*
*      @(#)vnconfig.c  8.1 (Berkeley) 12/15/93
*/

#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/mount.h>
#include <sys/buf.h>
#include <sys/disklabel.h>
#include <sys/disk.h>

#include <dev/vndvar.h>

#include <disktab.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>
#include <paths.h>
#include <limits.h>

#define VND_CONFIG      1
#define VND_UNCONFIG    2
#define VND_GET         3

/* with -l we always print at least this many entries */
#define DUMMY_FREE      4

static int      verbose = 0;
static int      readonly = 0;
static int      fileio = 0;
static int      force = 0;
static int      compressed = 0;
static int      minimum = DUMMY_FREE;
static char     *tabname;

static int      show(int, int, const char * const);
static int      config(char *, char *, char *, int);
static int      getgeom(struct vndgeom *, char *);
__dead static void      usage(void);
static void     show_unused(int);

int
main(int argc, char *argv[])
{
       int ch, rv, action = VND_CONFIG;
       char *end;
       unsigned long cnt;

       while ((ch = getopt(argc, argv, "Fcf:lm:rit:uvz")) != -1) {
               switch (ch) {
               case 'F':
                       force = 1;
                       break;
               case 'c':
                       action = VND_CONFIG;
                       break;
               case 'f':
                       if (setdisktab(optarg) == -1)
                               usage();
                       break;
               case 'l':
                       action = VND_GET;
                       break;
               case 'm':
                       cnt = strtoul(optarg, &end, 10);
                       if (cnt >= INT_MAX || end == optarg || *end != '\0')
                               usage();
                       minimum = (int)cnt;
                       break;
               case 'r':
                       readonly = 1;
                       break;
               case 'i':
                       fileio = 1;
                       break;
               case 't':
                       tabname = optarg;
                       break;
               case 'u':
                       action = VND_UNCONFIG;
                       break;
               case 'v':
                       verbose = 1;
                       break;
               case 'z':
                       compressed = 1;
                       readonly = 1;
                       break;
               default:
               case '?':
                       usage();
                       /* NOTREACHED */
               }
       }
       argc -= optind;
       argv += optind;

       if (action == VND_CONFIG) {
               if ((argc < 2 || argc > 3) ||
                   (argc == 3 && tabname != NULL))
                       usage();
               rv = config(argv[0], argv[1], (argc == 3) ? argv[2] : NULL,
                   action);
       } else if (action == VND_UNCONFIG) {
               if (argc != 1 || tabname != NULL)
                       usage();
               rv = config(argv[0], NULL, NULL, action);
       } else { /* VND_GET */
               int n, vdisk;
               const char *vn;
               char path[64];

               if (argc == 0) {
                       vn = "vnd0";

                       vdisk = opendisk(vn, O_RDONLY, path, sizeof(path), 0);
                       if (vdisk == -1) {
                               if (minimum == 0)
                                       return 1;
                               err(1, "open: %s", vn);
                       }

                       for (n = 0; show(vdisk, n, 0); n++)
                               continue;
                       while (n < minimum)
                               show_unused(n++);
                       close(vdisk);
                       return 0;
               }

               rv = 0;
               while (--argc >= 0) {
                       vn = *argv++;

                       vdisk = opendisk(vn, O_RDONLY, path, sizeof(path), 0);
                       if (vdisk == -1) {
                               warn("open: %s", vn);
                               rv = 1;
                               continue;
                       }

                       if (!show(vdisk, -1, vn))
                               rv = 1;
                       close(vdisk);
               }
       }
       return rv;
}

static void
show_unused(int n)
{
       if (minimum == 0)
               return;

       printf("vnd%d: not in use\n", n);
}

static int
show(int v, int n, const char * const name)
{
       struct vnd_user vnu;
       char *dev;
       struct statvfs *mnt;
       int i, nmount;

       vnu.vnu_unit = n;
       if (ioctl(v, VNDIOCGET, &vnu) == -1) {
               if (errno != ENXIO) {
                       if (n != -1)
                               err(1, "VNDIOCGET");
                       warn("%s: VNDIOCGET", name);
               }
               return 0;
       }

       if (vnu.vnu_ino == 0) {
               show_unused(vnu.vnu_unit);
               return -1;
       }

       printf("vnd%d: ", vnu.vnu_unit);

       dev = devname(vnu.vnu_dev, S_IFBLK);
       if (dev != NULL)
               nmount = getmntinfo(&mnt, MNT_NOWAIT);
       else {
               mnt = NULL;
               nmount = 0;
       }

       if (mnt != NULL) {
               for (i = 0; i < nmount; i++) {
                       if (strncmp(mnt[i].f_mntfromname, "/dev/", 5) == 0 &&
                           strcmp(mnt[i].f_mntfromname + 5, dev) == 0)
                               break;
               }
               if (i < nmount)
                       printf("%s (%s) ", mnt[i].f_mntonname,
                           mnt[i].f_mntfromname);
               else
                       printf("%s ", dev);
       }
       else if (dev != NULL)
               printf("%s ", dev);
       else
               printf("dev %llu,%llu ",
                   (unsigned long long)major(vnu.vnu_dev),
                   (unsigned long long)minor(vnu.vnu_dev));

       printf("inode %llu\n", (unsigned long long)vnu.vnu_ino);
       return 1;
}

static int
config(char *dev, char *file, char *geom, int action)
{
       struct vnd_ioctl vndio;
       struct disklabel *lp;
       char rdev[MAXPATHLEN + 1];
       int fd, rv;

       fd = opendisk(dev, O_RDWR, rdev, sizeof(rdev), 0);
       if (fd < 0) {
               warn("%s: opendisk", rdev);
               return (1);
       }

       memset(&vndio, 0, sizeof(vndio));
#ifdef __GNUC__
       rv = 0;                 /* XXX */
#endif

       vndio.vnd_file = file;
       if (geom != NULL) {
               rv = getgeom(&vndio.vnd_geom, geom);
               if (rv != 0)
                       errx(1, "invalid geometry: %s", geom);
               vndio.vnd_flags = VNDIOF_HASGEOM;
       } else if (tabname != NULL) {
               lp = getdiskbyname(tabname);
               if (lp == NULL)
                       errx(1, "unknown disk type: %s", tabname);
               vndio.vnd_geom.vng_secsize = lp->d_secsize;
               vndio.vnd_geom.vng_nsectors = lp->d_nsectors;
               vndio.vnd_geom.vng_ntracks = lp->d_ntracks;
               vndio.vnd_geom.vng_ncylinders = lp->d_ncylinders;
               vndio.vnd_flags = VNDIOF_HASGEOM;
       }

       if (readonly)
               vndio.vnd_flags |= VNDIOF_READONLY;

       if (compressed)
               vndio.vnd_flags |= VNF_COMP;

       if (fileio)
               vndio.vnd_flags |= VNDIOF_FILEIO;

       /*
        * Clear (un-configure) the device
        */
       if (action == VND_UNCONFIG) {
               if (force)
                       vndio.vnd_flags |= VNDIOF_FORCE;
               rv = ioctl(fd, VNDIOCCLR, &vndio);
#ifdef VNDIOOCCLR
               if (rv && errno == ENOTTY)
                       rv = ioctl(fd, VNDIOOCCLR, &vndio);
#endif
               if (rv)
                       warn("%s: VNDIOCCLR", rdev);
               else if (verbose)
                       printf("%s: cleared\n", rdev);
       }
       /*
        * Configure the device
        */
       if (action == VND_CONFIG) {
               int     ffd;

               ffd = open(file, readonly ? O_RDONLY : O_RDWR);
               if (ffd < 0) {
                       warn("%s", file);
                       rv = -1;
               } else {
                       (void) close(ffd);

                       rv = ioctl(fd, VNDIOCSET, &vndio);
#ifdef VNDIOOCSET
                       if (rv && errno == ENOTTY) {
                               rv = ioctl(fd, VNDIOOCSET, &vndio);
                               vndio.vnd_size = vndio.vnd_osize;
                       }
#endif
                       if (rv)
                               warn("%s: VNDIOCSET", rdev);
                       else if (verbose) {
                               printf("%s: %" PRIu64 " bytes on %s", rdev,
                                   vndio.vnd_size, file);
                               if (vndio.vnd_flags & VNDIOF_HASGEOM)
                                       printf(" using geometry %d/%d/%d/%d",
                                           vndio.vnd_geom.vng_secsize,
                                           vndio.vnd_geom.vng_nsectors,
                                           vndio.vnd_geom.vng_ntracks,
                                   vndio.vnd_geom.vng_ncylinders);
                               printf("\n");
                       }
               }
       }

       (void) close(fd);
       fflush(stdout);
       return (rv < 0);
}

static int
getgeom(struct vndgeom *vng, char *cp)
{
       char *secsize, *nsectors, *ntracks, *ncylinders;

#define GETARG(arg) \
       do { \
               if (cp == NULL || *cp == '\0') \
                       return (1); \
               arg = strsep(&cp, "/"); \
               if (arg == NULL) \
                       return (1); \
       } while (0)

       GETARG(secsize);
       GETARG(nsectors);
       GETARG(ntracks);
       GETARG(ncylinders);

#undef GETARG

       /* Too many? */
       if (cp != NULL)
               return (1);

#define CVTARG(str, num) \
       do { \
               num = strtol(str, &cp, 10); \
               if (*cp != '\0') \
                       return (1); \
       } while (0)

       CVTARG(secsize, vng->vng_secsize);
       CVTARG(nsectors, vng->vng_nsectors);
       CVTARG(ntracks, vng->vng_ntracks);
       CVTARG(ncylinders, vng->vng_ncylinders);

#undef CVTARG

       return (0);
}

static void
usage(void)
{
       const char *p = getprogname();
       (void)fprintf(stderr,
           "Usage: %s [-cirvz] [-f dsktab] [-t type] vnode_disk"
               " reg-file [geomspec]\n"
           "       %s -u [-Fv] vnode_disk\n"
           "       %s -l [-m num | vnode_disk...]\n", p, p, p);
       exit(1);
}