/* $Id: tmesh-cmds.c,v 1.6 2006/11/15 23:12:30 fredette Exp $ */

/* tmesh/tmesh-cmds.c - functions implementing the tmesh commands: */

/*
 * Copyright (c) 2003 Matt Fredette
 * All rights reserved.
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Matt Fredette.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 */

#include <tme/common.h>
_TME_RCSID("$Id: tmesh-cmds.c,v 1.6 2006/11/15 23:12:30 fredette Exp $");

/* includes: */
#include <tme/threads.h>
#include <stdlib.h>
#include "tmesh-impl.h"

/* macros: */

/* flags for ls: */
#define TMESH_LS_NORMAL		(0)
#define TMESH_LS_ALL		TME_BIT(0)
#define TMESH_LS_RECURSE	TME_BIT(1)
#define TMESH_LS_ABSOLUTE	TME_BIT(2)

/* the "source" command: */
static int
_tmesh_command_source(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  struct tmesh_io_stack *stack_new;
  struct tmesh_io *io_new;
  struct tmesh_io *io_old;
  int rc;
  
  /* allocate the new io stack member: */
  stack_new = tme_new(struct tmesh_io_stack, 1);
  io_new = &stack_new->tmesh_io_stack_io;

  /* initialize the new io: */
  io_new->tmesh_io_name = value->tmesh_parser_value_arg;
  io_new->tmesh_io_input_line = 0;

  /* call the current input source's open function: */
  io_old = &tmesh->tmesh_io_stack->tmesh_io_stack_io;
  rc = (*io_old->tmesh_io_open)(io_new, io_old, _output);

  /* if the open succeeded, push this new input source onto the stack,
     otherwise free it: */
  if (rc == TME_OK) {
    _tmesh_gc_release(tmesh, io_new->tmesh_io_name);
    stack_new->tmesh_io_stack_next = tmesh->tmesh_io_stack;
    tmesh->tmesh_io_stack = stack_new;
  }
  else {
    tme_free(stack_new);
  }

  /* done: */
  return (rc);
}

/* the "mkdir" command: */
static int
_tmesh_command_mkdir(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  struct tmesh_fs_dirent *parent, *entry;
  char *dirname;
  int rc;

  /* look up the new directory name: */
  dirname = value->tmesh_parser_value_pathname0;
  rc = _tmesh_fs_lookup(tmesh,
			&dirname,
			&parent, &entry,
			_output,
			TMESH_SEARCH_LAST_PART_OK);

  /* if the lookup succeeded: */
  if (rc == TME_OK) {

    /* if the new directory name already exists, we can't
       make it: */
    if (entry != NULL) {
      rc = EEXIST;
    }

    /* otherwise, make the new directory: */
    else {
      _tmesh_fs_mkdir(parent, tme_strdup(dirname));
    }
  }

  return (rc);
}

/* the "rmdir" command: */
static int
_tmesh_command_rmdir(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  struct tmesh_fs_dirent *parent, *entry, *dir;
  char *dirname;
  int rc;

  /* look up the directory: */
  dirname = value->tmesh_parser_value_pathname0;
  rc = _tmesh_fs_lookup(tmesh,
			&dirname,
			&parent, &entry,
			_output,
			TMESH_SEARCH_NORMAL);

  /* if the lookup succeeded: */
  if (rc == TME_OK) {

    /* if this pathname doesn't refer to a directory, we can't
       remove it: */
    if (entry->tmesh_fs_dirent_type != TMESH_FS_DIRENT_DIR) {
      rc = ENOTDIR;
    }

    /* otherwise, this is a directory: */
    else {
      dir = entry->tmesh_fs_dirent_value;

      /* if the directory isn't empty, we can't remove it: */
      if (dir->tmesh_fs_dirent_prev
	  != &dir->tmesh_fs_dirent_next->tmesh_fs_dirent_next) {
	rc = ENOTEMPTY;
      }

      /* you can't remove the current working directory,
	 or the "." or ".." directories: */
      else if (dir == tmesh->tmesh_cwd
	       || !strcmp(entry->tmesh_fs_dirent_name, ".")
	       || !strcmp(entry->tmesh_fs_dirent_name, "..")) {
	rc = EACCES;
      }

      /* otherwise, remove the directory: */
      else {

	/* unlink the directory in the parent: */
	_tmesh_fs_unlink(entry);

	/* free the "." and ".." directory entries: */
	tme_free(entry->tmesh_fs_dirent_next->tmesh_fs_dirent_name);
	tme_free(entry->tmesh_fs_dirent_next);
	tme_free(entry->tmesh_fs_dirent_name);
	tme_free(entry);
      }
    }
  }

  return (rc);
}

/* the "cd" command: */
static int
_tmesh_command_cd(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  struct tmesh_fs_dirent *parent, *entry;
  char *dirname;
  int rc;

  /* look up the directory: */
  dirname = value->tmesh_parser_value_pathname0;
  rc = _tmesh_fs_lookup(tmesh,
			&dirname,
			&parent, &entry,
			_output,
			TMESH_SEARCH_NORMAL);

  /* if the lookup succeeded: */
  if (rc == TME_OK) {

    /* if this pathname doesn't refer to a directory, we can't
       cd to it: */
    if (entry->tmesh_fs_dirent_type != TMESH_FS_DIRENT_DIR) {
      rc = ENOTDIR;
    }

    /* otherwise, this is a directory: */
    else {

      /* this is the new current working directory: */
      tmesh->tmesh_cwd = entry->tmesh_fs_dirent_value;
    }
  }

  return (rc);
}

/* the "pwd" command: */
static int
_tmesh_command_pwd(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  _tmesh_fs_pathname_dir(tmesh->tmesh_cwd, _output, NULL);
  tme_output_append(_output, "\n");
  return (TME_OK);
}

/* this outputs an argv: */
static void
_tmesh_ls_output_argv(char **_output, struct tmesh_parser_argv *argv, unsigned int skip)
{
  unsigned int argc;
  char **args;
  argc = argv->tmesh_parser_argv_argc;
  args = argv->tmesh_parser_argv_argv;
  assert(argc > 0 && argc >= skip);
  argc -= skip;
  args += skip;
  for (; argc-- > 0; ) {
    tme_output_append(_output, " ");
    tme_output_append(_output, "%s", *(args++));
  }
}

/* this lists an element: */
static void
_tmesh_ls_element(struct tmesh_fs_element *element,
		  char **_output,
		  int flags)
{
  int output_element_argv;
  struct tmesh_fs_element *element_other;
  struct tmesh_fs_element_conn *conn, *conn_other;

  /* we haven't yet output the element's argv: */
  output_element_argv = FALSE;

  /* loop over the element's connections: */
  for (conn = element->tmesh_fs_element_conns;
       conn != NULL;
       conn = conn->tmesh_fs_element_conn_next) {

    /* if we're not showing all connections, and this connection was
       made after this element was created, skip it: */
    if (!(flags & TMESH_LS_ALL)
	&& (conn->tmesh_fs_element_conn_gen
	    > element->tmesh_fs_element_gen)) {
      continue;
    }

    /* output this element's name and connection argv: */
    _tmesh_fs_pathname_element(element, _output, 
			       ((flags & TMESH_LS_ABSOLUTE)
				? NULL
				: element->tmesh_fs_element_parent));
    _tmesh_ls_output_argv(_output, &conn->tmesh_fs_element_conn_argv, 1);

    /* get the other side of this connection: */
    conn_other = conn->tmesh_fs_element_conn_other;
    element_other = conn_other->tmesh_fs_element_conn_element;
    tme_output_append(_output, " at ");      

    /* output the other element's name and connection argv: */
    _tmesh_fs_pathname_element(element_other, _output, 
			       ((flags & TMESH_LS_ABSOLUTE)
				? NULL
				: element->tmesh_fs_element_parent));
    _tmesh_ls_output_argv(_output, &conn_other->tmesh_fs_element_conn_argv, 1);

    /* if we haven't output the element's creation argv yet, do so: */
    if (!output_element_argv) {
      tme_output_append(_output, ":");
      _tmesh_ls_output_argv(_output, &element->tmesh_fs_element_argv, 0);
      output_element_argv = TRUE;
    }

    /* output a newline: */
    tme_output_append(_output, "\n");
  }

  /* if we haven't output the element's creation argv yet, do so: */
  if (!output_element_argv) {
    _tmesh_fs_pathname_element(element, _output, 
			       ((flags & TMESH_LS_ABSOLUTE)
				? NULL
				: element->tmesh_fs_element_parent));
    tme_output_append(_output, ":");
    _tmesh_ls_output_argv(_output, &element->tmesh_fs_element_argv, 0);
    tme_output_append(_output, "\n");
  }    
}

/* this lists a directory: */
static void
_tmesh_ls_dir(struct tmesh_fs_dirent *parent,
	      char **_output,
	      struct tmesh_fs_dirent *parent_top,
	      int flags)
{
  struct tmesh_fs_dirent *entry, *dir;
  struct tmesh_fs_element *element;
  int pass;

  /* list this directory: */
  for (pass = 0; ++pass < 2;) {

    /* if this is pass two, but we're not recursing, stop: */
    if (pass == 2
	&& !(flags & TMESH_LS_RECURSE)) {
      return;
    }

    /* loop over the entries in the directory: */
    entry = parent;
    do {
      
      /* dispatch on the directory entry's type: */
      switch (entry->tmesh_fs_dirent_type) {

	/* this is a subdirectory: */
      case TMESH_FS_DIRENT_DIR:
	dir = entry->tmesh_fs_dirent_value;

	/* handle a "." or ".." subdirectory: */
	if (!strcmp(entry->tmesh_fs_dirent_name, ".")
	    || !strcmp(entry->tmesh_fs_dirent_name, "..")) {
	
	  /* if we're listing all, and this is pass one: */
	  if ((flags & TMESH_LS_ALL)
	      && pass == 1) {
	    
	    /* output the directory's name: */
	    _tmesh_fs_pathname_dir(dir, _output, 
				   ((flags & TMESH_LS_ABSOLUTE)
				    ? NULL
				    : parent));
	    tme_output_append(_output, "\n");
	  }
	}

	/* otherwise this is a regular subdirectory: */
	else {

	  /* if this is pass one: */
	  if (pass == 1) {

	    /* output the directory's name: */
	    _tmesh_fs_pathname_dir(dir, _output, 
				   ((flags & TMESH_LS_ABSOLUTE)
				    ? NULL
				    : parent));
	    tme_output_append(_output, "/\n");
	  }

	  /* otherwise, this is pass two: */
	  else {

	    /* output the directory's name and recurse: */
	    tme_output_append(_output, "\n./");
	    _tmesh_fs_pathname_dir(dir, _output, 
				   ((flags & TMESH_LS_ABSOLUTE)
				    ? NULL
				    : parent_top));
	    tme_output_append(_output, ":\n");
	    _tmesh_ls_dir(dir, _output, parent_top, flags);
	  }
	}
	break;

	/* this is an element: */
      case TMESH_FS_DIRENT_ELEMENT:
	element = entry->tmesh_fs_dirent_value;
	_tmesh_ls_element(element, _output, flags);
	break;

      default: assert(FALSE);
      }
    
      /* if this was the last entry in the directory, we're done: */
      entry = entry->tmesh_fs_dirent_next;
    } while (entry != parent);
  }
}

/* the "ls" command: */
static int
_tmesh_command_ls(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  char *opts, *pathname, opt;
  struct tmesh_fs_dirent *parent, *entry;
  int type;
  void *what;
  int rc;
  int flags;

  /* take apart any options: */
  opts = value->tmesh_parser_value_strings[0];
  flags = TMESH_LS_NORMAL;
  if (opts != NULL) {
    assert(*opts == '-');
    for (; (opt = *(++opts)) != '\0'; ) {
      switch (opt) {

	/* 'a' lists all directory entries, and all connections on elements: */
      case 'a': flags |= TMESH_LS_ALL; break;

	/* 'l' lists absolute pathnames for elements: */
      case 'l': flags |= TMESH_LS_ABSOLUTE; break;

	/* 'R' recurses: */
      case 'R': flags |= TMESH_LS_RECURSE; break;

	/* an unknown option: */
      default:
	tme_output_append(_output,
			  "ls: %s '-%c'\n",
			  _("invalid option"),
			  opt);
	return (EINVAL);
      }
    }
  }

  /* if a path name is given, look it up: */
  pathname = value->tmesh_parser_value_strings[1];
  if (pathname != NULL) {
    rc = _tmesh_fs_lookup(tmesh,
			  &pathname,
			  &parent, &entry,
			  _output,
			  TMESH_SEARCH_NORMAL);
    
    /* if the lookup failed: */
    if (rc != TME_OK) {
      return (rc);
    }

    /* get what we're listing: */
    type = entry->tmesh_fs_dirent_type;
    what = entry->tmesh_fs_dirent_value;
  }

  /* otherwise, list the cwd: */
  else {
    type = TMESH_FS_DIRENT_DIR;
    what = tmesh->tmesh_cwd;
  }

  /* dispatch on the type: */
  switch (type) {
  case TMESH_FS_DIRENT_DIR:
    _tmesh_ls_dir(what, _output, what, flags);
    break;
  case TMESH_FS_DIRENT_ELEMENT:
    _tmesh_ls_element(what, _output, flags);
    break;
  default: assert(FALSE);
  }
    
  return (TME_OK);
}

/* the "connect" command: */
static int
_tmesh_command_connect(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  struct tmesh_fs_dirent *parent, *entry;
  char *pathname;
  struct tmesh_fs_element *element0, *element1;
  char **element0_args;
  char **element1_args;
  char **creation_args;
  struct tmesh_fs_element_conn *conn0, *conn1, **prev;
  int which;
  int rc;
  
  /* get and NULL-terminate all argument lists: */
  element0_args = value->tmesh_parser_value_argvs[0].tmesh_parser_argv_argv;
  assert(element0_args != NULL);
  element0_args[value->tmesh_parser_value_argvs[0].tmesh_parser_argv_argc] = NULL;
  element1_args = value->tmesh_parser_value_argvs[1].tmesh_parser_argv_argv;
  if (element1_args != NULL) {
    element1_args[value->tmesh_parser_value_argvs[1].tmesh_parser_argv_argc] = NULL;
  }
  creation_args = value->tmesh_parser_value_argvs[2].tmesh_parser_argv_argv;
  if (creation_args != NULL) {
    creation_args[value->tmesh_parser_value_argvs[2].tmesh_parser_argv_argc] = NULL;
  }
  
  /* check any other element to connect to: */
  element1 = NULL;
  if (element1_args != NULL) {

    /* look up the element: */
    pathname = element1_args[0];
    rc = _tmesh_fs_lookup(tmesh,
			  &pathname,
			  &parent, &entry,
			  _output,
			  TMESH_SEARCH_NORMAL);
    if (rc != TME_OK) {
      return (rc);
    }

    /* this must be an element: */
    if (entry->tmesh_fs_dirent_type != TMESH_FS_DIRENT_ELEMENT) {
      tme_output_append(_output, "%s", element1_args[0]);
      return (ENOTSOCK);
    }
    element1 = entry->tmesh_fs_dirent_value;
  }

  /* check the element: */
  pathname = element0_args[0];
  rc = _tmesh_fs_lookup(tmesh,
			&pathname,
			&parent, &entry,
			_output,
			TMESH_SEARCH_LAST_PART_OK);
  if (rc != TME_OK) {
    return (rc);
  }
  
  /* if the name exists: */
  if (entry != NULL) {

    /* it must be an element: */
    if (entry->tmesh_fs_dirent_type != TMESH_FS_DIRENT_ELEMENT) {
      tme_output_append(_output, "%s", value->tmesh_parser_value_pathname0);
      return (ENOTSOCK);
    }
    element0 = entry->tmesh_fs_dirent_value;

    /* we must have been given no creation arguments: */
    if (creation_args != NULL) {
      return (EEXIST);
    }
  }

  /* otherwise, the name doesn't exist: */
  else {

    /* we must have been given some creation arguments: */
    if (creation_args == NULL) {
      return (ENOENT);
    }

    /* allocate the new element: */
    element0 = tme_new0(struct tmesh_fs_element, 1);

    /* set this element's parent and link it into the directory: */
    element0->tmesh_fs_element_parent = parent;
    entry = _tmesh_fs_link(parent, tme_strdup(pathname), 
			   TMESH_FS_DIRENT_ELEMENT, element0);

    /* open the log for this element: */
    pathname = NULL;
    _tmesh_fs_pathname_element(element0, &pathname, NULL);
    (*tmesh->tmesh_support.tmesh_support_log_open)
      (&tmesh->tmesh_support,
       &element0->tmesh_fs_element_element.tme_element_log_handle,
       pathname,
       creation_args[0]);
    tme_free(pathname);

    /* create the element: */
    rc = tme_element_new(&element0->tmesh_fs_element_element,
			 (const char **) creation_args,
			 NULL,
			 _output);
    
    /* if the creation failed: */
    if (rc != TME_OK) {

      /* close the log for this element: */
      (*tmesh->tmesh_support.tmesh_support_log_close)
	(&tmesh->tmesh_support,
	 &element0->tmesh_fs_element_element.tme_element_log_handle);

      /* unlink and free this entry: */
      _tmesh_fs_unlink(entry);
      tme_free(entry->tmesh_fs_dirent_name);
      tme_free(entry);
      return (rc);
    }

    /* the creation succeeded.  set the generation number and preserve
       the creation arguments: */
    element0->tmesh_fs_element_gen = ++tmesh->tmesh_gen_last;
    element0->tmesh_fs_element_argv = value->tmesh_parser_value_argvs[2];
    element0->tmesh_fs_element_element.tme_element_args = element0_args;
    _tmesh_gc_release_argv(tmesh, &element0->tmesh_fs_element_argv);
  }

  /* if we have another element, make a connection: */
  if (element1 != NULL) {
    rc = tme_element_connect(&element0->tmesh_fs_element_element, 
			     (const char **) element0_args,
			     &element1->tmesh_fs_element_element, 
			     (const char **) element1_args,
			     _output, &which);

    /* if the connection failed: */
    if (rc != TME_OK) {
      return (rc);
    }

    /* allocate the new connections: */
    for (prev = &element0->tmesh_fs_element_conns;
	 (conn0 = *prev) != NULL;
	 prev = &conn0->tmesh_fs_element_conn_next);
    *prev = conn0 = tme_new0(struct tmesh_fs_element_conn, 1);
    for (prev = &element1->tmesh_fs_element_conns;
	 (conn1 = *prev) != NULL;
	 prev = &conn1->tmesh_fs_element_conn_next);
    *prev = conn1 = tme_new0(struct tmesh_fs_element_conn, 1);

    /* remember the connections: */
    conn0->tmesh_fs_element_conn_element = element0;
    conn0->tmesh_fs_element_conn_gen = tmesh->tmesh_gen_last;
    conn0->tmesh_fs_element_conn_other = conn1;
    conn0->tmesh_fs_element_conn_argv = value->tmesh_parser_value_argvs[0];
    _tmesh_gc_release_argv(tmesh, &conn0->tmesh_fs_element_conn_argv);
    conn1->tmesh_fs_element_conn_element = element1;
    conn1->tmesh_fs_element_conn_gen = tmesh->tmesh_gen_last;
    conn1->tmesh_fs_element_conn_other = conn0;
    conn1->tmesh_fs_element_conn_argv = value->tmesh_parser_value_argvs[1];
    _tmesh_gc_release_argv(tmesh, &conn1->tmesh_fs_element_conn_argv);
  }

  /* done: */
  return (rc);
}

/* the "mv" command: */
static int
_tmesh_command_mv(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  /* TBD: */
  abort();
}

/* the "rm" command: */
static int
_tmesh_command_rm(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  /* TBD: */
  abort();
}

/* the "command" command: */
static int
_tmesh_command_command(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  struct tmesh_fs_dirent *parent, *entry;
  char *pathname;
  struct tmesh_fs_element *element;
  char **element_args;
  int rc;

  /* look up the element: */
  element_args = value->tmesh_parser_value_argvs[0].tmesh_parser_argv_argv;
  assert(element_args != NULL);
  element_args[value->tmesh_parser_value_argvs[0].tmesh_parser_argv_argc] = NULL;
  pathname = element_args[0];
  rc = _tmesh_fs_lookup(tmesh,
			&pathname,
			&parent, &entry,
			_output,
			TMESH_SEARCH_NORMAL);

  /* if the lookup succeeded: */
  if (rc == TME_OK) {

    /* if this pathname doesn't refer to an element, we can't
       power it: */
    if (entry->tmesh_fs_dirent_type != TMESH_FS_DIRENT_ELEMENT) {
      rc = ENOTSOCK;
    }

    /* otherwise, this is an element: */
    else {
      element = entry->tmesh_fs_dirent_value;

      /* if it doesn't support the "command" command: */
      if (element->tmesh_fs_element_element.tme_element_command == NULL) {
	rc = EOPNOTSUPP;
      }

      /* otherwise, run the command: */
      else {
	rc = ((*element->tmesh_fs_element_element.tme_element_command)
	      (&element->tmesh_fs_element_element,
	       (const char **) element_args,
	       _output));
      }
    }
  }

  return (rc);
}

/* the "log" command: */
static int
_tmesh_command_log(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  struct tmesh_fs_dirent *parent, *entry;
  char *pathname;
  struct tmesh_fs_element *element;
  char **element_args;
#ifndef TME_NO_LOG
  unsigned long log_level;
  char *p1;
#endif /* !TME_NO_LOG */
  int rc;

  /* look up the element: */
  element_args = value->tmesh_parser_value_argvs[0].tmesh_parser_argv_argv;
  assert(element_args != NULL);
  element_args[value->tmesh_parser_value_argvs[0].tmesh_parser_argv_argc] = NULL;
  pathname = element_args[0];
  rc = _tmesh_fs_lookup(tmesh,
			&pathname,
			&parent, &entry,
			_output,
			TMESH_SEARCH_NORMAL);

  /* if the lookup succeeded: */
  if (rc == TME_OK) {

    /* if this pathname doesn't refer to an element, we can't
       set its logging level: */
    if (entry->tmesh_fs_dirent_type != TMESH_FS_DIRENT_ELEMENT) {
      rc = ENOTSOCK;
    }

    /* otherwise, this is an element: */
    else {
      element = entry->tmesh_fs_dirent_value;

      /* if logging is not supported: */
#ifdef TME_NO_LOG
      rc = EOPNOTSUPP;
#else  /* !TME_NO_LOG */

      /* assume our arguments are invalid: */
      rc = EINVAL;

      /* check our arguments: */
      if (element_args[1] != NULL
	  && element_args[2] == NULL) {

	/* assume a log level of zero: */
	log_level = 0;

	/* "off" means a log level of zero: */
	if (!strcmp(element_args[1], "off")) {
	  rc = TME_OK;
	}

	/* any other argument must be a log level: */
	else {
	  log_level = strtoul(element_args[1], &p1, 0);
	  if (p1 != element_args[1]
	      && *p1 == '\0') {
	    rc = TME_OK;
	  }
	}

	/* set the log level: */
	if (rc == TME_OK) {
	  element->tmesh_fs_element_element.tme_element_log_handle.tme_log_handle_level_max
	    = log_level;
	}
      }
#endif /* !TME_NO_LOG */
    }
  }

  return (rc);
}

/* the "alias" command: */
static int
_tmesh_command_alias(struct tmesh *tmesh, struct tmesh_parser_value *value, char **_output)
{
  struct tmesh_fs_dirent *parent, *entry;
  struct tmesh_fs_element *element;
  char *oldname;
  char *newname;
  int rc;

  /* look up the existing element: */
  oldname = value->tmesh_parser_value_pathname1;
  rc = _tmesh_fs_lookup(tmesh,
			&oldname,
			&parent, &entry,
			_output,
			TMESH_SEARCH_NORMAL);

  /* if the lookup failed, return now: */
  if (rc != TME_OK) {
    return (rc);
  }

  /* if this pathname doesn't refer to an element, we can't alias it: */
  if (entry->tmesh_fs_dirent_type != TMESH_FS_DIRENT_ELEMENT) {
    return (ENOTSOCK);
  }

  /* get the element to alias: */
  element = entry->tmesh_fs_dirent_value;

  /* look up the new name: */
  newname = value->tmesh_parser_value_pathname0;
  rc = _tmesh_fs_lookup(tmesh,
			&newname,
			&parent, &entry,
			_output,
			TMESH_SEARCH_LAST_PART_OK);
  if (rc != TME_OK) {
    return (rc);
  }

  /* if the name exists: */
  if (entry != NULL) {
    return (EEXIST);
  }

  /* create the alias: */
  entry = _tmesh_fs_link(parent, tme_strdup(newname), 
			 TMESH_FS_DIRENT_ELEMENT, element);
  return (TME_OK);
}

/* this evaluates one or more commands: */
int
tmesh_eval(void *_tmesh, char **_output, int *_yield)
{
  struct tmesh *tmesh;
  struct tmesh_parser_value value;
  int rc;
  int (*command_func) _TME_P((struct tmesh *, struct tmesh_parser_value *, char **));
  
  /* recover our structure: */
  tmesh = (struct tmesh *) _tmesh;

  /* start the output: */
  *_output = NULL;

  /* we don't have any memory to gc: */
  tmesh->tmesh_gc_record = NULL;

  /* parse a command: */
  rc = _tmesh_yyparse(tmesh, &value, _output, _yield);

  /* if the parse succeeded: */
  if (rc == TME_OK && !*_yield) {

    /* dispatch on the command: */
    switch (value.tmesh_parser_value_token) {
    default: assert(FALSE);
    case TMESH_COMMAND_NOP: command_func = NULL; break;
    case TMESH_COMMAND_SOURCE: command_func = _tmesh_command_source; break;
    case TMESH_COMMAND_MKDIR: command_func = _tmesh_command_mkdir; break;
    case TMESH_COMMAND_RMDIR: command_func = _tmesh_command_rmdir; break;
    case TMESH_COMMAND_CD: command_func = _tmesh_command_cd; break;
    case TMESH_COMMAND_PWD: command_func = _tmesh_command_pwd; break;
    case TMESH_COMMAND_LS: command_func = _tmesh_command_ls; break;
    case TMESH_COMMAND_CONNECT: command_func = _tmesh_command_connect; break;
    case TMESH_COMMAND_RM: command_func = _tmesh_command_rm; break;
    case TMESH_COMMAND_MV: command_func = _tmesh_command_mv; break;
    case TMESH_COMMAND_COMMAND: command_func = _tmesh_command_command; break;
    case TMESH_COMMAND_LOG: command_func = _tmesh_command_log; break;
    case TMESH_COMMAND_ALIAS: command_func = _tmesh_command_alias; break;
    }
    
    /* call the function: */
    if (command_func != NULL) {
      rc = (*command_func)(tmesh, &value, _output);
    }
  }

  /* garbage collect: */
  _tmesh_gc_gc(tmesh);

  /* done: */
  return (rc);
}

/* this creates a new shell: */
void *
tmesh_new(const struct tmesh_support *support, const struct tmesh_io *first_io)
{
  struct tmesh *tmesh;

  /* allocate the new shell: */
  tmesh = tme_new(struct tmesh, 1);
  
  /* start the io stack: */
  tmesh->tmesh_io_stack = tme_new0(struct tmesh_io_stack, 1);
  tmesh->tmesh_io_stack->tmesh_io_stack_io = *first_io;
  
  /* create the root directory: */
  tmesh->tmesh_root = _tmesh_fs_mkdir(NULL, NULL);

  /* set the cwd: */
  tmesh->tmesh_cwd = tmesh->tmesh_root;

  /* save the support: */
  tmesh->tmesh_support = *support;

  /* done: */
  return (tmesh);
}
