/*      $NetBSD: amfs_nfsx.c,v 1.1.1.3 2015/01/17 16:34:15 christos Exp $       */

/*
* Copyright (c) 1997-2014 Erez Zadok
* Copyright (c) 1990 Jan-Simon Pendry
* Copyright (c) 1990 Imperial College of Science, Technology & Medicine
* Copyright (c) 1990 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Jan-Simon Pendry at Imperial College, London.
*
* 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.
*
*
* File: am-utils/amd/amfs_nfsx.c
*
*/

/*
* NFS hierarchical mounts
*
* TODO: Re-implement.
*/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <am_defs.h>
#include <amd.h>

/*
* The rfs field contains a list of mounts to be done from
* the remote host.
*/
typedef struct amfs_nfsx_mnt {
 mntfs *n_mnt;
 int n_error;
} amfs_nfsx_mnt;

struct amfs_nfsx {
 int nx_c;                     /* Number of elements in nx_v */
 amfs_nfsx_mnt *nx_v;          /* Underlying mounts */
 amfs_nfsx_mnt *nx_try;
 am_node *nx_mp;
};

/* forward definitions */
static char *amfs_nfsx_match(am_opts *fo);
static int amfs_nfsx_mount(am_node *am, mntfs *mf);
static int amfs_nfsx_umount(am_node *am, mntfs *mf);
static int amfs_nfsx_init(mntfs *mf);

/*
* Ops structure
*/
am_ops amfs_nfsx_ops =
{
 "nfsx",
 amfs_nfsx_match,
 amfs_nfsx_init,
 amfs_nfsx_mount,
 amfs_nfsx_umount,
 amfs_error_lookup_child,
 amfs_error_mount_child,
 amfs_error_readdir,
 0,                            /* amfs_nfsx_readlink */
 0,                            /* amfs_nfsx_mounted */
 0,                            /* amfs_nfsx_umounted */
 find_nfs_srvr,                /* XXX */
 0,                            /* amfs_nfsx_get_wchan */
 /* FS_UBACKGROUND| */ FS_AMQINFO,     /* nfs_fs_flags */
#ifdef HAVE_FS_AUTOFS
 AUTOFS_NFSX_FS_FLAGS,
#endif /* HAVE_FS_AUTOFS */
};


static char *
amfs_nfsx_match(am_opts *fo)
{
 char *xmtab;
 char *ptr;
 int len;

 if (!fo->opt_rfs) {
   plog(XLOG_USER, "amfs_nfsx: no remote filesystem specified");
   return FALSE;
 }

 if (!fo->opt_rhost) {
   plog(XLOG_USER, "amfs_nfsx: no remote host specified");
   return FALSE;
 }

 /* set default sublink */
 if (fo->opt_sublink == NULL || fo->opt_sublink[0] == '\0') {
   ptr = strchr(fo->opt_rfs, ',');
   if (ptr && ptr > (fo->opt_rfs + 1))
     fo->opt_sublink = strnsave(fo->opt_rfs + 1, ptr - fo->opt_rfs - 1);
 }

 /*
  * Remove trailing ",..." from ${fs}
  * After deslashifying, overwrite the end of ${fs} with "/"
  * to make sure it is unique.
  */
 if ((ptr = strchr(fo->opt_fs, ',')))
   *ptr = '\0';
 deslashify(fo->opt_fs);

 /*
  * Bump string length to allow trailing /
  */
 len = strlen(fo->opt_fs);
 fo->opt_fs = xrealloc(fo->opt_fs, len + 1 + 1);
 ptr = fo->opt_fs + len;

 /*
  * Make unique...
  */
 *ptr++ = '/';
 *ptr = '\0';

 /*
  * Determine magic cookie to put in mtab
  */
 xmtab = str3cat((char *) NULL, fo->opt_rhost, ":", fo->opt_rfs);
 dlog("NFSX: mounting remote server \"%s\", remote fs \"%s\" on \"%s\"",
      fo->opt_rhost, fo->opt_rfs, fo->opt_fs);

 return xmtab;
}


static void
amfs_nfsx_prfree(opaque_t vp)
{
 struct amfs_nfsx *nx = (struct amfs_nfsx *) vp;
 int i;

 for (i = 0; i < nx->nx_c; i++) {
   mntfs *m = nx->nx_v[i].n_mnt;
   if (m)
     free_mntfs(m);
 }

 XFREE(nx->nx_v);
 XFREE(nx);
}


static int
amfs_nfsx_init(mntfs *mf)
{
 /*
  * mf_info has the form:
  *   host:/prefix/path,sub,sub,sub
  */
 int i;
 int glob_error;
 struct amfs_nfsx *nx;
 int asked_for_wakeup = 0;

 nx = (struct amfs_nfsx *) mf->mf_private;

 if (nx == 0) {
   char **ivec;
   char *info = NULL;
   char *host;
   char *pref;
   int error = 0;

   info = xstrdup(mf->mf_info);
   if (info == NULL)
     return errno;

   host = strchr(info, ':');
   if (!host) {
     error = EINVAL;
     goto errexit;
   }
   pref = host + 1;
   host = info;

   /*
    * Split the prefix off from the suffices
    */
   ivec = strsplit(pref, ',', '\'');

   /*
    * Count array size
    */
   for (i = 0; ivec[i]; i++)
     /* nothing */;

   nx = ALLOC(struct amfs_nfsx);
   mf->mf_private = (opaque_t) nx;
   mf->mf_prfree = amfs_nfsx_prfree;

   nx->nx_c = i - 1;           /* i-1 because we don't want the prefix */
   nx->nx_v = (amfs_nfsx_mnt *) xmalloc(nx->nx_c * sizeof(amfs_nfsx_mnt));
   nx->nx_mp = NULL;
   {
     char *mp = NULL;
     char *xinfo = NULL;
     char *fs = mf->mf_fo->opt_fs;
     char *rfs = NULL;
     for (i = 0; i < nx->nx_c; i++) {
       char *path = ivec[i + 1];
       rfs = str3cat(rfs, pref, "/", path);
       /*
        * Determine the mount point.
        * If this is the root, then don't remove
        * the trailing slash to avoid mntfs name clashes.
        */
       mp = str3cat(mp, fs, "/", rfs);
       normalize_slash(mp);
       deslashify(mp);
       /*
        * Determine the mount info
        */
       xinfo = str3cat(xinfo, host, *path == '/' ? "" : "/", path);
       normalize_slash(xinfo);
       if (pref[1] != '\0')
         deslashify(xinfo);
       dlog("amfs_nfsx: init mount for %s on %s", xinfo, mp);
       nx->nx_v[i].n_error = -1;
       nx->nx_v[i].n_mnt = find_mntfs(&nfs_ops, mf->mf_fo, mp, xinfo, "", mf->mf_mopts, mf->mf_remopts);
       /* propagate the on_autofs flag */
       nx->nx_v[i].n_mnt->mf_flags |= mf->mf_flags & MFF_ON_AUTOFS;
     }
     XFREE(rfs);
     XFREE(mp);
     XFREE(xinfo);
   }

   XFREE(ivec);
 errexit:
   XFREE(info);
   if (error)
     return error;
 }

 /*
  * Iterate through the mntfs's and call
  * the underlying init routine on each
  */
 glob_error = 0;

 for (i = 0; i < nx->nx_c; i++) {
   amfs_nfsx_mnt *n = &nx->nx_v[i];
   mntfs *m = n->n_mnt;
   int error = 0;
   if (m->mf_ops->fs_init && !(mf->mf_flags & MFF_RESTART))
     error = m->mf_ops->fs_init(m);
   /*
    * if you just "return error" here, you will have made a failure
    * in any submounts to fail the whole group.  There was old unused code
    * here before.
    */
   if (error > 0)
     n->n_error = error;

   else if (error < 0) {
     glob_error = -1;
     if (!asked_for_wakeup) {
       asked_for_wakeup = 1;
       sched_task(wakeup_task, (opaque_t) mf, get_mntfs_wchan(m));
     }
   }
 }

 return glob_error;
}


static void
amfs_nfsx_cont(int rc, int term, opaque_t arg)
{
 mntfs *mf = (mntfs *) arg;
 struct amfs_nfsx *nx = (struct amfs_nfsx *) mf->mf_private;
 am_node *mp = nx->nx_mp;
 amfs_nfsx_mnt *n = nx->nx_try;

 n->n_mnt->mf_flags &= ~(MFF_ERROR | MFF_MOUNTING);
 mf->mf_flags &= ~MFF_ERROR;

 /*
  * Wakeup anything waiting for this mount
  */
 wakeup(get_mntfs_wchan(n->n_mnt));

 if (rc || term) {
   if (term) {
     /*
      * Not sure what to do for an error code.
      */
     plog(XLOG_ERROR, "mount for %s got signal %d", n->n_mnt->mf_mount, term);
     n->n_error = EIO;
   } else {
     /*
      * Check for exit status
      */
     errno = rc;               /* XXX */
     plog(XLOG_ERROR, "%s: mount (amfs_nfsx_cont): %m", n->n_mnt->mf_mount);
     n->n_error = rc;
   }
   free_mntfs(n->n_mnt);
   n->n_mnt = new_mntfs();
   n->n_mnt->mf_error = n->n_error;
   n->n_mnt->mf_flags |= MFF_ERROR;
 } else {
   /*
    * The mount worked.
    */
   mf_mounted(n->n_mnt, FALSE); /* FALSE => don't free the n_mnt->am_opts */
   n->n_error = 0;
 }

 /*
  * Do the remaining bits
  */
 if (amfs_nfsx_mount(mp, mf) >= 0)
   wakeup(get_mntfs_wchan(mf));
}


static int
try_amfs_nfsx_mount(opaque_t mv)
{
 mntfs *mf = (mntfs *) mv;
 struct amfs_nfsx *nx = (struct amfs_nfsx *) mf->mf_private;
 am_node *mp = nx->nx_mp;
 int error;

 error = mf->mf_ops->mount_fs(mp, mf);

 return error;
}


static int
amfs_nfsx_remount(am_node *am, mntfs *mf, int fg)
{
 struct amfs_nfsx *nx = (struct amfs_nfsx *) mf->mf_private;
 amfs_nfsx_mnt *n;
 int glob_error = -1;

 /* Save the am_node pointer for later use */
 nx->nx_mp = am;

 /*
  * Iterate through the mntfs's and mount each filesystem
  * which is not yet mounted.
  */
 for (n = nx->nx_v; n < nx->nx_v + nx->nx_c; n++) {
   mntfs *m = n->n_mnt;

   if (m->mf_flags & MFF_MOUNTING)
     break;

   if (m->mf_flags & MFF_MOUNTED) {
     mf_mounted(m, FALSE);     /* FALSE => don't free the m->am_opts */
     n->n_error = glob_error = 0;
     continue;
   }

   if (n->n_error < 0) {
     /* Create the mountpoint, if and as required */
     if (!(m->mf_flags & MFF_MKMNT) && m->mf_fsflags & FS_MKMNT) {
       if (!mkdirs(m->mf_mount, 0555))
         m->mf_flags |= MFF_MKMNT;
     }

     dlog("calling underlying mount on %s", m->mf_mount);
     if (!fg && foreground && (m->mf_fsflags & FS_MBACKGROUND)) {
       m->mf_flags |= MFF_MOUNTING;
       dlog("backgrounding mount of \"%s\"", m->mf_info);
       nx->nx_try = n;
       run_task(try_amfs_nfsx_mount, (opaque_t) m, amfs_nfsx_cont, (opaque_t) mf);
       n->n_error = -1;
       return -1;
     } else {
       dlog("foreground mount of \"%s\" ...", mf->mf_info);
       n->n_error = m->mf_ops->mount_fs(am, m);
     }

     if (n->n_error > 0)
       dlog("underlying fmount of %s failed: %s", m->mf_mount, strerror(n->n_error));

     if (n->n_error == 0) {
       glob_error = 0;
     } else if (glob_error < 0) {
       glob_error = n->n_error;
     }
   }
 }

 return glob_error < 0 ? 0 : glob_error;
}


static int
amfs_nfsx_mount(am_node *am, mntfs *mf)
{
 return amfs_nfsx_remount(am, mf, FALSE);
}


/*
* Unmount an NFS hierarchy.
* Note that this is called in the foreground
* and so may hang under extremely rare conditions.
*/
static int
amfs_nfsx_umount(am_node *am, mntfs *mf)
{
 struct amfs_nfsx *nx = (struct amfs_nfsx *) mf->mf_private;
 amfs_nfsx_mnt *n;
 int glob_error = 0;

 /*
  * Iterate in reverse through the mntfs's and unmount each filesystem
  * which is mounted.
  */
 for (n = nx->nx_v + nx->nx_c - 1; n >= nx->nx_v; --n) {
   mntfs *m = n->n_mnt;
   /*
    * If this node has not been messed with
    * and there has been no error so far
    * then try and unmount.
    * If an error had occurred then zero
    * the error code so that the remount
    * only tries to unmount those nodes
    * which had been successfully unmounted.
    */
   if (n->n_error == 0) {
     dlog("calling underlying fumount on %s", m->mf_mount);
     n->n_error = m->mf_ops->umount_fs(am, m);
     if (n->n_error) {
       glob_error = n->n_error;
       n->n_error = 0;
     } else {
       /*
        * Make sure remount gets this node
        */
       n->n_error = -1;
     }
   }
 }

 /*
  * If any unmounts failed then remount the
  * whole lot...
  */
 if (glob_error) {
   glob_error = amfs_nfsx_remount(am, mf, TRUE);
   if (glob_error) {
     errno = glob_error;       /* XXX */
     plog(XLOG_USER, "amfs_nfsx: remount of %s failed: %m", mf->mf_mount);
   }
   glob_error = EBUSY;
 } else {
   /*
    * Remove all the mount points
    */
   for (n = nx->nx_v; n < nx->nx_v + nx->nx_c; n++) {
     mntfs *m = n->n_mnt;
     dlog("calling underlying umounted on %s", m->mf_mount);
     if (m->mf_ops->umounted)
       m->mf_ops->umounted(m);

     if (n->n_error < 0) {
       if (m->mf_fsflags & FS_MKMNT) {
         (void) rmdirs(m->mf_mount);
         m->mf_flags &= ~MFF_MKMNT;
       }
     }
     free_mntfs(m);
     n->n_mnt = NULL;
     n->n_error = -1;
   }
 }

 return glob_error;
}