/*
 * lookup_file.c
 *
 * Module for Linux automountd to access a plain file automount map
 */

#include <stdio.h>
#include <malloc.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include <syslog.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>

#define MODULE_LOOKUP
#include "automount.h"

#define MAPFMT_DEFAULT "sun"

#define MODPREFIX "lookup(file): "

#define MAPENT_MAX_LEN 4095

struct lookup_context {
  char *mapname;
  struct parse_mod *parse;
};

int lookup_version = AUTOFS_LOOKUP_VERSION; /* Required by protocol */

int lookup_init(char *mapfmt, int argc, char **argv, void **context)
{
  struct lookup_context *ctxt;

  if ( !(*context = ctxt = malloc(sizeof(struct lookup_context))) ) {
    syslog(LOG_CRIT, MODPREFIX "malloc: %m");
    return 1;
  }
  if ( argc < 1 ) {
    syslog(LOG_CRIT, MODPREFIX "No map name");
    return 1;
  }
  ctxt->mapname = argv[0];

  if (ctxt->mapname[0] != '/') {
    syslog(LOG_CRIT, MODPREFIX "file map %s is not an absolute pathname",
	   ctxt->mapname);
    return 1;
  }

  if ( access(ctxt->mapname, R_OK) ) {
    syslog(LOG_WARNING, MODPREFIX "file map %s missing or not readable",
	   ctxt->mapname);
  }

  if ( !mapfmt )
    mapfmt = MAPFMT_DEFAULT;

  return !(ctxt->parse = open_parse(mapfmt,MODPREFIX,argc-1,argv+1));
}

int lookup_mount(char *root, char *name, int name_len, void *context)
{
  struct lookup_context *ctxt = (struct lookup_context *) context;
  int ch, nch;
  char mapent[MAPENT_MAX_LEN+1], *p, *nptr;
  int mapent_len;
  FILE *f;
  enum {
    st_begin, st_compare, st_star, st_badent, st_entspc, st_getent
  } state;
  enum { got_nothing, got_star, got_real } getting, gotten;

  syslog(LOG_DEBUG, MODPREFIX "looking up %s", name);

  chdir("/");			/* If this is not here the filesystem stays
				   busy, for some reason... */
  f = fopen(ctxt->mapname, "r");
  if ( !f ) {
    syslog(LOG_ERR, MODPREFIX "could not open map file %s", ctxt->mapname);
    return 1;
  }
  
  state = st_begin;
  gotten = got_nothing;

  /* Shut up gcc */
  p = nptr = NULL;
  mapent_len = 0;
  getting = got_nothing;

  while ( (ch = getc(f)) != EOF ) {
    if ( ch == '\\' ) {
      /* Handle continuation lines */
      if ( (nch = getc(f)) == '\n' )
	continue;
      ch = nch;			/* Treat the first backslash as an escape */
    }

    switch(state) {
    case st_begin:
      if ( isspace(ch) )
	;
      else if ( ch == '#' )
	state = st_badent;
      else if ( (char)ch == name[0] ) {
	state = st_compare;
	nptr = name+1;
      }	else if ( ch == '*' )
	state = st_star;
      else
	state = st_badent;
      break;

    case st_compare:
      if ( ch == '\n' )
	state = st_begin;
      else if ( isspace(ch) && !*nptr ) {
	getting = got_real;
	state = st_entspc;
      } else if ( (char)ch != *(nptr++) )
	state = st_badent;
      break;

    case st_star:
      if ( ch == '\n' )
	state = st_begin;
      else if ( isspace(ch) && gotten < got_star ) {
	getting = got_star;
	state = st_entspc;
      } else
	state = st_badent;
      break;

    case st_badent:
      if ( ch == '\n' )
	state = st_begin;
      break;

    case st_entspc:
      if ( ch == '\n' )
	state = st_begin;
      else if ( !isspace(ch) ) {
	state = st_getent;
	p = mapent;
	gotten = getting;
	*(p++) = ch;
	mapent_len = 1;
      }
      break;

    case st_getent:
      if ( ch == '\n' ) {
	state = st_begin;
	if ( gotten == got_real )
	  goto got_it;		/* No point in parsing the rest of the file */
      } else if ( mapent_len < MAPENT_MAX_LEN ) {
	mapent_len++;
	*(p++) = ch;
      }
      break;
    }
  }
got_it:
  fclose(f);

  if ( gotten == got_nothing ) {
    syslog(LOG_NOTICE, MODPREFIX "lookup for %s failed", name);
    return 1;			/* Didn't found anything */
  }
  *p = '\0';			/* Null-terminate */

  syslog(LOG_DEBUG, MODPREFIX "%s -> %s", name, mapent);

  return ctxt->parse->parse_mount(root,name,name_len,mapent,ctxt->parse->context); 
}

int lookup_done(void *context)
{
  struct lookup_context *ctxt = (struct lookup_context *) context;
  int rv = close_parse(ctxt->parse);
  free(ctxt);
  return rv;
}
