#ident "$Id: //depot/autofs-4.0/modules/lookup_program.c#3 $"
/* ----------------------------------------------------------------------- *
 *   
 *  lookup_program.c - module for Linux automount to access an
 *                     automount map via a query program 
 *
 *   Copyright 1997 Transmeta Corporation - All Rights Reserved
 *   Copyright 1999-2000 Jeremy Fitzhardinge <jeremy@goop.org>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
 *   USA; either version 2 of the License, or (at your option) any later
 *   version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <signal.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MODULE_LOOKUP
#include "automount.h"

#define MAPFMT_DEFAULT "sun"

#define MODPREFIX "lookup(program): "

#define MAPENT_MAX_LEN 4095

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

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

int lookup_init(const char *mapfmt, int argc, const char * const *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 "program map %s is not an absolute pathname",
	   ctxt->mapname);
    return 1;
  }

  if ( access(ctxt->mapname, X_OK) ) {
    syslog(LOG_WARNING, MODPREFIX "program map %s missing or not executable",
	   ctxt->mapname);
  }

  if ( !mapfmt )
    mapfmt = MAPFMT_DEFAULT;

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

int lookup_mount(const char *root, const char *name, int name_len,
		 void *context)
{
  struct lookup_context *ctxt = (struct lookup_context *) context;
  char mapent[MAPENT_MAX_LEN+1], *mapp;
  char keyent[MAPENT_MAX_LEN+1], *keyp;
  char errbuf[1024], *errp;
  char ch;
  int pipefd[2], epipefd[2];
  pid_t f;
  int files_left;
  int status, errs;
  fd_set readfds, ourfds;
  struct maps {
	  const char *key;
	  const char *map;
	  struct maps *next;
  } *maps = NULL, *mapend = NULL;
  enum state { st_key, st_space, st_map } state;
  int quoted = 0;

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

  /* We don't use popen because we don't want to run /bin/sh plus we
     want to send stderr to the syslog, and we don't use spawnl()
     because we need the pipe hooks */

  if ( pipe(pipefd) ) {
    syslog(LOG_ERR, MODPREFIX "pipe: %m");
    return 1;
  }
  if ( pipe(epipefd) ) {
    close(pipefd[0]);
    close(pipefd[1]);
    return 1;
  }
  f = fork();
  if ( f < 0 ) {
    close(pipefd[0]);
    close(pipefd[1]);
    close(epipefd[0]);
    close(epipefd[1]);
    syslog(LOG_ERR, MODPREFIX "fork: %m");
    return 1;
  } else if ( f == 0 ) {
    reset_signals();
    close(pipefd[0]);
    close(epipefd[0]);
    dup2(pipefd[1],STDOUT_FILENO);
    dup2(epipefd[1],STDERR_FILENO);
    close(pipefd[1]);
    close(epipefd[1]);
    execl(ctxt->mapname, ctxt->mapname, name, NULL);
    _exit(255);			/* execl() failed */
  }
  close(pipefd[1]);
  close(epipefd[1]);

  keyp = keyent;
  mapp = mapent;
  errp = errbuf;
  state = st_key;

  FD_ZERO(&ourfds);
  FD_SET(pipefd[0],&ourfds);
  FD_SET(epipefd[0],&ourfds);

  files_left = 2;

  while (files_left != 0) {
    readfds = ourfds;
    if ( select(OPEN_MAX, &readfds, NULL, NULL, NULL) < 0 && 
	 errno != EINTR )
      break;

    /* Parse maps from stdout */
    if ( FD_ISSET(pipefd[0],&readfds) ) {
      if ( read(pipefd[0], &ch, 1) < 1 ) {
	FD_CLR(pipefd[0], &ourfds);
	files_left--;
      }
      if (!quoted && ch == '\\') {
        quoted = 1;
        continue;
      }
      switch(state)
      {
      case st_key:
	      if (!quoted && isspace(ch)) {
		      *keyp = '\0';
		      state = st_space;
	      }
	      else if (keyp - keyent < MAPENT_MAX_LEN)
		      *keyp++ = ch;
	      break;
      case st_space:
	      if (quoted || !isspace(ch)) {
		      *mapp++ = ch;
		      state = st_map;
	      }
	      break;
      case st_map:
	      if (ch == '\n') {
		      struct maps *m = alloca(sizeof(*m));
		      *mapp = '\0';
		      m->map = strdup(mapent);
		      m->key = strdup(keyent);
		      m->next = NULL;
		      if (mapend != NULL)
			      mapend->next = m;
		      else
			      maps = m;
		      mapend = m;

		      mapp = mapent;
		      keyp = keyent;
		      state = st_key;
	      } else if (mapp - mapent < MAPENT_MAX_LEN)
		      *mapp++ = ch;
	      break;
      }
      quoted = 0;
    }
    /* Deal with stderr */
    if ( FD_ISSET(epipefd[0],&readfds) ) {
      if ( read(epipefd[0], &ch, 1) < 1 ) {
	FD_CLR(epipefd[0], &ourfds);
	files_left--;
      } else if ( ch == '\n' ) {
	*errp = '\0';
	if ( errbuf[0] )
	  syslog(LOG_NOTICE, ">> %s", errbuf);
	errp = errbuf;
      } else {
	if ( errp >= &errbuf[1023] ) {
	  *errp = '\0';
	  syslog(LOG_NOTICE, ">> %s", errbuf);
	  errp = errbuf;
	}
	*(errp++) = ch;
      }
    }
  }

  if ( mapp )
    *mapp = '\0';
  if ( errp > errbuf ) {
    *errp = '\0';
    syslog(LOG_NOTICE, ">> %s", errbuf);
  }

  close(pipefd[0]);
  close(epipefd[0]);

  if ( waitpid(f,&status,0) != f ) {
    syslog(LOG_ERR, MODPREFIX "waitpid: %m");
    return 1;
  }

  if ( maps == NULL || !WIFEXITED(status) || WEXITSTATUS(status) != 0 ) {
    syslog(LOG_NOTICE, MODPREFIX "lookup for %s failed", name);
    return 1;
  }

  errs = 0;
  for(; maps != NULL; maps = maps->next)
  {
    int ret;
    syslog(LOG_DEBUG, MODPREFIX "%s (%s) -> %s", name, maps->key, maps->map);

    ret = ctxt->parse->parse_mount(root,maps->key,strlen(maps->key),
				   maps->map,
				   ctxt->parse->context); 
    free(maps->key);
    free(maps->map);
    if (ret)
	    errs++;
  }
  return errs;
}

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