/*
 * mount_nfs.c
 *
 * Module for Linux automountd to mount an NFS filesystem, with fallback to
 * symlinking if the path is local
 *
 */

#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <syslog.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>

#define MODULE_MOUNT
#include "automount.h"

#define MODPREFIX "mount(nfs): "
int mount_version = AUTOFS_MOUNT_VERSION; /* Required by protocol */

static int udpproto;
static short port_discard;

int mount_init(void **context)
{
  struct protoent *udp;
  struct servent *port_dis;

  /* These are context independent */
  udp = getprotobyname("udp");
  udpproto = udp ? udp->p_proto : 0;
  port_dis = getservbyname("discard","udp");
  if ( port_dis )
    port_discard = port_dis->s_port;
  else
    port_discard = htons(9);    /* 9 is the standard discard port */

  return 0;
}

int mount_mount(char *root, char *name, int name_len, char *what,
		char *fstype, char *options, void *context)
{
  char *colon, **haddr, *fullpath;
  struct hostent *he;
  struct sockaddr_in saddr, laddr;
  int sock, local, err;
  size_t len;

  colon = strchr(what, ':');
  if ( !colon ) {
    /* No colon, take this as a symlink (local) entry */
    syslog(LOG_DEBUG, MODPREFIX "entry %s -> %s: no colon, assume local",
	   name, what);
    chdir(root);
    err = symlink(what, name);
    if ( err && errno == EEXIST )
      err = 0;
    if ( err )
      syslog(LOG_NOTICE, MODPREFIX "symlink %s: %m", name);
    chdir("/");
    return err ? 1 : 0;
  }

  *colon = '\0';
  if ( !(he = gethostbyname(what)) ) {
    syslog(LOG_NOTICE, MODPREFIX "entry %s: host %s: lookup failure",
	   name, what);
    return 1;			/* No such host */
  }

  /* Probe to see if we are the local host.  Open a UDP socket and see
     if the local address is the same as the remote one */

  local = 0;
  for ( haddr = he->h_addr_list ; *haddr ; haddr++ ) {
    sock = socket(AF_INET, SOCK_DGRAM, udpproto);
    if ( sock < 0 ) {
      syslog(LOG_ERR, MODPREFIX "socket: %m");
      return 1;
    }
    saddr.sin_family = AF_INET;
    bcopy(*haddr, &saddr.sin_addr, he->h_length);
    saddr.sin_port = port_discard;

    len = sizeof(laddr);
    if ( connect(sock, (struct sockaddr *) &saddr, sizeof(saddr)) < 0 
	 || getsockname(sock, (struct sockaddr *) &laddr, &len) < 0 ) {
      syslog(LOG_ERR, MODPREFIX "connect+getsockname failed for %s", name);
      close(sock);
      return 1;
    }
    close(sock);
    
    if ( !memcmp(&saddr.sin_addr,&laddr.sin_addr,he->h_length) ) {
      local = 1;
      break;
    }
  }

  if ( local ) {
    /* Local host -- do a symlink */
    
    syslog(LOG_DEBUG, MODPREFIX "%s is local, symlinking", name);
    chdir(root);
    err = symlink(colon+1, name);
    if ( err && errno == EEXIST )
      err = 0;
    if ( err )
      syslog(LOG_NOTICE, MODPREFIX "symlink %s: %m", name);
    chdir("/");
    return err ? 1 : 0;
  } else {
    /* Not a local host - do a mount */

    fullpath = alloca(strlen(root)+name_len+2);
    if ( !fullpath ) {
      syslog(LOG_ERR, MODPREFIX "alloca: %m");
      return 1;
    }
    sprintf(fullpath, "%s/%s", root, name);
    
    *colon = ':';
    syslog(LOG_DEBUG, MODPREFIX "calling mkdir %s", fullpath);
    if ( mkdir(fullpath, 0555) && errno != EEXIST ) {
      syslog(LOG_NOTICE, MODPREFIX "mkdir %s failed: %m", name);
      return 1;
    }
    
    if ( options ) {
      syslog(LOG_DEBUG, MODPREFIX "calling mount -t nfs -o %s %s %s",
	     options, what, fullpath);
      err = spawnl(_PATH_MOUNT, _PATH_MOUNT, "-t", "nfs", "-o", options,
		   what, fullpath, NULL);
    } else {
      syslog(LOG_DEBUG, MODPREFIX "calling mount -t nfs %s %s", what, fullpath);
      err = spawnl(_PATH_MOUNT, _PATH_MOUNT, "-t", "nfs", what, fullpath, NULL);
    }
    if ( err ) {
      rmdir(fullpath);
      syslog(LOG_NOTICE, MODPREFIX "nfs: mount failure %s on %s",
	     what, fullpath);
      return 1;
    } else {
      syslog(LOG_DEBUG, MODPREFIX "mounted %s on %s", what, fullpath);
      return 0;
    }
  }
}

int mount_done(void *context)
{
  return 0;
}
