/*
 * parse_sun.c
 *
 * Module for Linux automountd to parse a Sun-format automounter map
 */

#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 <ctype.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <netinet/in.h>

#define MODULE_PARSE
#include "automount.h"

#define MODPREFIX "parse(sun): "
int parse_version = AUTOFS_PARSE_VERSION; /* Required by protocol */

static struct mount_mod *mount_nfs = NULL;
static int init_ctr = 0;

struct substvar {
  char *def;			/* Define variable */
  char *val;			/* Value to replace with */
  struct substvar *next;
};

struct parse_context {
  char *optstr;			/* Mount options */
  struct substvar *subst;	/* $-substitutions */
};

struct utsname un;
char processor[65];		/* Not defined on Linux, so we make our own */

static struct substvar		/* Predefined variables: tail of link chain */
  sv_arch   = { "ARCH",   un.machine, NULL },
  sv_cpu    = { "CPU",    processor,  &sv_arch },
  sv_host   = { "HOST",   un.machine, &sv_cpu },
  sv_osname = { "OSNAME", un.sysname, &sv_host },
  sv_osrel  = { "OSREL",  un.release, &sv_osname },
  sv_osvers = { "OSVERS", un.version, &sv_osrel };

/* Free all storage associated with this context */
static void kill_context(struct parse_context *ctxt)
{
  struct substvar *sv, *nsv;

  sv = ctxt->subst;
  while ( sv != &sv_osvers ) {
    nsv = sv->next;
    free(sv);
    sv = nsv;
  }
  
  if ( ctxt->optstr )
    free(ctxt->optstr);

  free(ctxt);
}

/* Find the $-variable matching a certain string fragment */
static struct substvar *findvar(struct substvar *sv, char *str, int len)
{
  while ( sv ) {
    if ( !strncmp(str,sv->def,len) && sv->def[len] == '\0' )
      return sv;
    sv = sv->next;
  }
  return NULL;
}

/* $- and &-expand a Sun-style map entry and return the length of the entry.
   If "dst" is NULL, just count the length. */
static int expandsunent(char *src, char *dst, char *key, struct substvar *svc)
{
  struct substvar *sv;
  int len, l;
  char *p;

  len = 0;

  while ( *src ) {
    switch ( *src ) {
    case '&':
      l = strlen(key);
      if ( dst ) {
	strcpy(dst,key);
	dst += l;
      }
      len += l;
      src++;
      break;

    case '$':
      if ( *(++src) == '{' ) {
	p = strchr(++src,'}');
	if ( !p ) {
	  /* Ignore rest of string */
	  if ( dst ) *dst = '\0';
	  return len;
	}
	sv = findvar(svc, src, p-src);
	if ( sv ) {
	  l = strlen(sv->val);
	  if ( dst ) {
	    strcpy(dst,sv->val);
	    dst += l;
	  }
	  len += l;
	}
	src = p+1;
      } else {
	p = src;
	while ( isalnum(*p) || *p == '_' ) p++;
	sv = findvar(svc, src, p-src);
	if ( sv ) {
	  l = strlen(sv->val);
	  if ( dst ) {
	    strcpy(dst,sv->val);
	    dst += l;
	  }
	  len += l;
	}
	src = p;
      }
      break;

    default:
      if ( dst ) *(dst++) = *src;
      src++;
      len++;
      break;
    }
  }
  if ( dst ) *dst = '\0';
  return len;
}

/* Skip whitespace in a string; if we hit a #, consider the rest of the
   entry a comment */
char *skipspace(char *whence)
{
  while(1) {
    switch(*whence) {
    case ' ':
    case '\b':
    case '\t':
    case '\n':
    case '\v':
    case '\f':
    case '\r':
      whence++;
      break;
    case '\0':
    case '#':
      return NULL;		/* End of string */
    default:
      return whence;
    }
  }
}

/* Get the length of a chunk delimitered by whitespace */
int chunklen(char *whence)
{
  int n = 0;
  while(1) {
    switch(*whence) {
    case ' ':
    case '\b':
    case '\t':
    case '\n':
    case '\v':
    case '\f':
    case '\r':
    case '#':
    case '\0':
      return n;
    default:
      break;
    }
    n++;
    whence++;
  }
}

int parse_init(int argc, char **argv, void **context)
{
  struct parse_context *ctxt;
  struct substvar *sv;
  char *noptstr;
  int optlen, len;
  int i;

  /* Get processor information for predefined escapes */

  if ( !init_ctr ) {
    uname(&un);
    /* uname -p is not defined on Linux.  Make it the same as uname -m,
       except make it return i386 on all x86 (x >= 3) */
    strcpy(processor, un.machine);
    if ( processor[0] == 'i' && processor[1] >= '3' &&
	 !strcmp(processor+2, "86") )
      processor[1] = '3';
  }

  /* Set up context and escape chain */
  
  if ( !(ctxt = (struct parse_context *) malloc(sizeof(struct parse_context))) ) {
    syslog(LOG_CRIT, MODPREFIX "malloc: %m");
    return 1;
  }
  *context = (void *) ctxt;
  
  ctxt->subst = &sv_osvers;

  /* Look for options and capture, and create new defines if we need to */

  ctxt->optstr = NULL;
  optlen = 0;

  for ( i = 0 ; i < argc ; i++ ) {
    if ( argv[i][0] == '-' ) {
      switch(argv[i][1]) {
      case 'D':
	sv = malloc(sizeof(struct substvar));
	if ( !sv ) {
	  syslog(LOG_ERR, MODPREFIX "malloc: %m");
	  break;
	}
	if ( argv[i][2] )	  
	  sv->def = strdup(argv[i]+2);
	else if ( ++i < argc )
	  sv->def = strdup(argv[i]);
	else {
	  free(sv);
	  break;
	}

	if ( !sv->def ) {
	  syslog(LOG_ERR, MODPREFIX "strdup: %m");
	  free(sv);
	} else {
	  sv->val = strchr(sv->def, '=');
	  if ( sv->val )
	    *(sv->val++) = '\0';
	  else
	    sv->val = "";
	  
	  sv->next = ctxt->subst;
	  ctxt->subst = sv;
	}
	break;
      default:
	syslog(LOG_ERR, MODPREFIX "unknown option: %s", argv[i]);
	break;
      }
    } else {
      len = strlen(argv[i]);
      if ( ctxt->optstr ) {
	noptstr = (char *) realloc(ctxt->optstr, optlen+len+2);
	if ( !noptstr )
	  break;
	noptstr[optlen] = ',';
	strcpy(noptstr+len+1, argv[i]);
	optlen += len+1;
      } else {
	noptstr = (char *) malloc(len+1);
	strcpy(noptstr, argv[i]);
	optlen = len;
      }
      if ( !noptstr ) {
	kill_context(ctxt);
	syslog(LOG_CRIT, MODPREFIX "%m");
	return 1;
      }
      ctxt->optstr = noptstr;
      syslog(LOG_DEBUG, MODPREFIX "init gathered options: %s", ctxt->optstr);
    }
  }

  /* We only need this once */

  if ( !mount_nfs )
    if ( (mount_nfs = open_mount("nfs", MODPREFIX)) ) {
      init_ctr++;
      return 0;
    } else {
      kill_context(ctxt);
      return 1;
    }
  else
    return 0;
}

int parse_mount(char *root, char *name, int name_len, char *mapent, void *context)
{
  struct parse_context *ctxt = (struct parse_context *) context;
  char *pmapent, *options, *noptions, *p;
  int mapent_len, rv;
  int l, optlen;

  mapent_len = expandsunent(mapent,NULL,name,ctxt->subst);
  pmapent = malloc(mapent_len + 1);
  if (!pmapent) {
    syslog(LOG_ERR, MODPREFIX "malloc: %m");
    return 1;
  }
  expandsunent(mapent,pmapent,name,ctxt->subst);

  syslog(LOG_DEBUG, "expanded entry: %s", pmapent);

  options = ctxt->optstr;	/* No options found */
  optlen = options ? strlen(options) : 0;

  p = pmapent;
  while ( (p = skipspace(p)) ) {
    if ( *p == '-' ) {
      l = chunklen(++p);
      if ( !l )
	continue;
      if ( options ) {
	if ( options != ctxt->optstr ) {
	  noptions = realloc(options, optlen+l+2);
	  if ( !noptions ) {
	    free(pmapent);
	    free(options);
	    syslog(LOG_ERR, MODPREFIX "realloc: %m");
	    return 1;
	  }
	} else {
	  noptions = malloc(optlen+l+2);
	  if ( !noptions ) {
	    free(pmapent);
	    syslog(LOG_ERR, MODPREFIX "malloc: %m");
	    return 1;
	  }	  
	  memcpy(noptions, ctxt->optstr, optlen+1);
	}
	noptions[optlen++] = ',';
	memcpy(noptions+optlen, p, l);
	noptions[optlen += l] = '\0';
	options = noptions;
      } else {
	options = malloc(l+1);
	if ( !options ) {
	  free(pmapent);
	  syslog(LOG_ERR, MODPREFIX "malloc: %m");
	  return 1;
	}
	memcpy(options, p, l);
	options[optlen = l] = '\0';
      }
      p += l;
      syslog(LOG_DEBUG, MODPREFIX "gathered options: %s", options);
    } else if ( *p == '/' ) {
      /* Uh-oh, a multientry; don't support now, but ****FIXME**** */
      free(pmapent);
      if ( options != ctxt->optstr ) free(options);
      syslog(LOG_NOTICE, MODPREFIX "entry %s is a multipath entry", name);
      return 1;
    } else {
      p[chunklen(p)] = '\0';	/* We don't use anything after the entry */
      syslog(LOG_DEBUG, MODPREFIX "core of entry: %s", p);
      break;			/* We have the beef of the entry, run w/it */
    }
  }

  rv = mount_nfs->mount_mount(root, name, name_len, p, "nfs", options,
			      mount_nfs->context);
  free(pmapent);
  if ( options != ctxt->optstr ) free(options);
  return rv;
}

int parse_done(void *context)
{
  int rv;

  kill_context((struct parse_context *) context);

  if ( --init_ctr == 0 ) {
    rv = mount_nfs->mount_done(mount_nfs->context);
    mount_nfs = NULL;
    return rv;
  } else
    return 0;
}
