#if 0
ifdef notdef
*
* POrtable Dodgy Filesystems in Userland (this used to be hacK!)
*                alias podfuk (v2)
* pretend you are venus cache manager, and export virtual files to kernel
* (plus, you can get kernel patch there; with it you will not have to cd
* /overlay first. Very handy.)
*
* Copyright 1999 Pavel Machek, pavel@ucw.cz
*
* You can get more info at:
* http://atrey.karlin.mff.cuni.cz/~pavel/podfuk/podfuk.html
*
* Distribute under GPL version 2 or later.
*
* Version 2.0 - it works, modulo strange things in tar archives.
* Version 2.1 - tar archives + ftp are now fixed.
* Version 2.2 - made it Makefile
* Version 2.3 - attempt to make it deadlock free, no more complains for strange fids
* Version 2.4 - make a bit more demonic
* Version 2.5 - roll in changes by qrczak@knm.org.pl - handle uids right
* Version 2.6 - add url, fix mknod, do not use ^#, fix paths
* Version 2.7 - check return value for select + small fixes by Michael Hart <mavrik@ihug.co.nz>
* Version 2.8 - merged small fixes from Pat Beirne <patb@corel.ca>, preparation for rw support
* Version 2.9 - added rw support, progress bar possibility: Pat Beirne <patb@corel.ca>
*
* !!This version has a problem, buried in VFS. A user copies two files,
*   A and B, both on the VFS. The sequence: open(A,O_RDONLY), open(B,WR_ONLY),
*   read(A), write(B), close(B), close(A). The first open() causes the
*   VFS to read to a local copy. The second open() makes a local empty temp file.
*   The first close() will write the temp file to the VFS AND FLUSH THE
*   VFS DIRECTORY CACHE!. When the second close() is called, it re-reads the
*   file from the VFS. Wasteful but not fatal. Bad.!!
*
*   This version does not clean up the v_list linked list. Bad. !!
*
* Notice this file is *evil*. It is both valid c source and working
* Makefile. You might want to ln -s podfuk.c Makefile. 
*
* Edit MIDNIGHT= to point at root of mc sources, you need sources of
* mc-4.5.5 or newer for this to work. (Youll miss file callback.h if 
* you use 4.5.30 or older - get it from homepage.)
*
* Edit KERNEL= to point include/ directory of your 2.2 kernel source
*
endif

# DEBUG turns on diagnostic printing
# USE_MONITOR makes calls to an X progress bar
CFLAGS = -DUSE_MONITOR -DCOREL_DEF -Wall
#CFLAGS = -DDEBUG -DUSE_MONITOR -DCOREL_DEF -Wall -g
#CFLAGS = -DDEBUG -Wall -DNODAEMON

MIDNIGHT=/home/patb/projects/mc-4.5.40

KERNEL=/usr/src/linux/include

VFS=$(MIDNIGHT)/vfs

LINUX=/usr/src/linux

all:	podfuk

podfuk:	$(VFS)/libvfs.so podfuk.c
	gcc podfuk.c `glib-config --libs --cflags` -I$(MIDNIGHT) -I$(KERNEL) $(VFS)/libvfs.so -o podfuk  $(CFLAGS)

podfuk-static: podfuk.c
	gcc podfuk.c `glib-config --libs --cflags` -I$(MIDNIGHT) -I$(KERNEL) $(VFS)/tcputil.so $(VFS)/fish.so $(VFS)/ftpfs.so $(VFS)/mcfs.so $(VFS)/utilvfs.so $(VFS)/local.so $(VFS)/vfs.so $(VFS)/tar.so $(VFS)/sfs.so $(VFS)/names.so $(VFS)/container.so $(VFS)/extfs.so $(VFS)/util-alone.so $(VFS)/util.sor $(VFS)/utilunix.sor $(VFS)/direntry.so -o podfuk-static $(CFLAGS)

/dev/cfs0:
	mknod /dev/cfs0 c 67 5

/overlay:
	mkdir /overlay

run: /dev/cfs0 /overlay podfuk
	{ cd $(LINUX)/fs/coda; insmod coda.o; }
	sleep 1
	{ ./podfuk; } &
	sleep 1
	mount /dev/cfs0 /overlay -tcoda

clean:
	rm podfuk podfuk-static

$(MIDNIGHT)/vfs/libvfs.so:
	echo 'Attempting to make libvfs.so from midnight'
	cd $(MIDNIGHT)/vfs
	make libvfs.so

ifdef donotdefine
#endif

/* TTD:
 add timeouts for non-operative ftp sites
 a lookup request precipitates a login & read;
               is this neccessary for the root?
 how are we going to clean up the vfid chain?
 	there could be an "infinite" number of entries
 */

   /* Coda/Venus in a nutshell:
    * Coda is a filesystem, lives in the kernel
    * Venus is an app; podfuk is a replacement for venus
    * When a coda mount needs service, it sends a message
    *   up to podfuk through a pipe
    * Coda/Podfuk both open /dev/cfs0 and make ioctl calls
    * Message includes ViceFid (coda file/dir handle)
    *   which is: u32 volume-id, u32 node-id, u32 unique-num
    * Each file has an attribute struct: very similar to inode
    *
    * Typ sequence of calls to podfuk from coda:
    * file read: access(), open(), close()
    *    (the read() is done through the local file)
    * dir listing: access(), open(), lookup(), access(), close()
    * file write: lookup(file), access(parent), create(file), getattr(),
    *    open(), close(), setattr()
    *
* disk scheme:
*       /mnt/ftp/aliasname
*               has placeholders such as
*               /mnt/ftp/corel -> /mnt/.mcvfs/#ftp:ftp.corel.com
*       /mnt/.mcvfs is mounted as -t coda
*/

#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/time.h>
#include <sys/fsuid.h>
#include <signal.h>
#include <unistd.h>
#include <glib.h>

#include <linux/coda.h>

#define VFS_STANDALONE 1
#include <vfs/vfs.h>
#include <vfs/callback.h>

#ifndef USE_VFS
#error Midnight was not configured to use vfs. Please configure it to use it. 
#endif

#define PARANOIA 1

#define VERSION_STRING "2.9.1"

#ifdef DEBUG
# define DEBUG_PRINT printf
# define FLUSH_TIME 15
#else
# define DEBUG_PRINT if (0) printf
# define FLUSH_TIME 300
#endif

#if COREL_DEF
# define MOUNT_POINT_STRING "/mnt/.mcvfs"
#else
# define MOUNT_POINT_STRING "/overlay"
#endif

/* There is nothing magic about these constants. I just selected them
 * so that kernel does not complain too loudly.  */
#define MY_VOL   0x01234567
#define MY_VNODE 0xffffffff
#define MY_INODE 0x12345678
#define MK_FID(f,v,n,u) {f.Volume=v;f.Vnode=n;f.Unique=u;}

/* missing from vfs.h */
vfs * vfs_rosplit (char *path);
extern int ftpfs_use_passive_connections;
extern int ftpfs_retry_seconds;


/* our handle to the pipe */
int cfs;

/* our local object: NOTE: we try to do as little as possible
 * and trust that vfs will handle caching for us */
struct v_list {
  char *real_name;
  char *local_name;
  short usage;
  char  has_changed; // dirty bit
  char  is_dir;
  char  is_created; // marker for files which do not yet exist at the other end
  char  is_trunc;   // marker for files which are to be sent to the other end
  /* timestamp ?? */
};
// accessors
#define v_usage(x) ((struct v_list*)x.Unique)->usage
#define v_created(x) ((struct v_list*)x.Unique)->is_created

/* a linked list of the above objects */
GList *pList;

int count = 0;

struct stat generic_stat = { 0 /* dev */, 0 /* pad */,
	0 /* inode */, __S_IFREG | 0666 /* mode */,
	0, 0, /* uid, gid */ 0 /* device */, 0 /* pad */,
	0 /* size */, 1024 /* blksize */ }; /* rest are 0 */

#ifdef USE_MONITOR
int  monitor_handle;
char monitor_buf[300];
#endif

/******************** shutdown and timeout **********************************/
void signal_handler(int signo)
{
  mc_vfs_done();
  exit(0);
}

void flush_VFids(void)
{
  GList *pWalk, *pPrev;

  struct coda_purgefid_out cmd;

  DEBUG_PRINT( "cleaning the local VFid cache\n" );
  /* note: we are deleting elements, so we have to walk carefully */
  /* walk backward so that we purge children, then parents */
  for ( pWalk = g_list_last(pList), pPrev = g_list_previous(pWalk);
        pPrev;
        pWalk = pPrev, pPrev = g_list_previous(pPrev)) {
    if (((struct v_list*)pWalk->data)->usage == 0) {
      struct v_list* v = (struct v_list*) pWalk->data;
      DEBUG_PRINT( "purging list element <%s>\n", v->real_name );

      /* !!! OOPS !!! if a shell has a cwd on our mount point, then this
       purge will not work, leaving broken VFids !!! */
      cmd.oh.opcode = CODA_PURGEFID;
      cmd.oh.unique = 0;
      MK_FID(cmd.CodaFid,MY_VOL,MY_VNODE,(long)v);
      write(cfs, &cmd, sizeof(cmd));
      
      free( v->real_name );
      if (v->local_name) free( v->local_name);
      free(pWalk->data);
      pList = g_list_remove_link(pList, pWalk);
      g_list_free(pWalk);
    }
  }
}

/******************** config ************************************************/
void
set_global_var( const char* section, const char* var, const char* val )
{
  if ( !strcmp( var, "ftpfs_use_passive_connections" ) )
    ftpfs_use_passive_connections = ( val[0] == '1' );
  if ( !strcmp( var, "logfile" ) && val[0] ) {
      ftpfs_set_debug( val ); printf("logfile set to %s\n",val); }
  if ( !strcmp( var, "ftpfs_login_timeout") )
    ftpfs_retry_seconds = atoi(val);
}

#if GLIB_MINOR_VERSION==1
inline char* g_strstrip(char*c) {
	char *p;
	for (p=c; *p && isspace(*p); p++);
	memmove(c,p,strlen(p)+1);
	for (p=c+strlen(c); p>c && isspace(p[-1]); p--);
	*p = 0;
	return c;}

#endif
/* load and parse an ini file: handles sections, comments and params */
void
load_profile( const char* config_file, void (*setter) (const char*, const char*, const char*) )
{
  char* section = 0;
  FILE* f = fopen( config_file, "r" );
  if ( !f ) return;
  while (!feof(f)) {
    char buf[250];
    char *p = buf;
    fgets(buf,250,f);
    p = g_strstrip(buf);
    if (p[0] == '[') {
          g_free(section);
	  g_strdelimit(p+1,"]",'\0');
          section = g_strdup(p+1);
          //printf("got section header [%s]\n", section);
    } else if (p[0] == '#') {
          //printf("got a comment <%s>\n",p+1);
    } else {
          char *pp;
          pp = strchr(buf,'=');
	  if (pp>buf && pp<(buf+strlen(buf)-1)) {
	     *pp=0;
             (*setter)( section, g_strstrip(buf), g_strstrip(pp+1) );
          //printf("got strings <%s> and <%s>\n",buf,pp+1);
	  }
    }
  }
  g_free(section);
  fclose(f);
}

/*************************** debugging callback ****************************/
#ifdef USE_MONITOR
void progress_func( char* m)
{
  if (monitor_handle==-1) return;
  /* Send the progress string to the monitor.
   * Add an extra zero to delimit the string. */
  write(monitor_handle,m,strlen(m)+1);
}
#endif // USE_MONITOR

/************************** file name maintenance **************************/

char *
look_name( ViceFid id )
{
  if ((id.Volume != MY_VOL) || (id.Vnode != MY_VNODE)) {
    printf( "Bad handle passed %lx/%lx/%lx\n", id.Volume, id.Vnode, id.Unique );
    exit(1);
  }
  if (PARANOIA) {
    GList *pWalk;
    for( pWalk = pList; pWalk; pWalk = g_list_next(pWalk) ) {
        if (pWalk->data == (gpointer) id.Unique)
            break;
    }
    if (!pWalk) {
      printf( "Bad handle passed %lx/%lx/%lx\n", id.Volume, id.Vnode, id.Unique );
      return 0; //exit(1);
    }
  } /* paranoia */
  return ((struct v_list*)id.Unique)->real_name;
}

ViceFid
alloc_vfid( char *name )
{
  ViceFid res;
  struct v_list *h = g_new0(struct v_list,1);
  pList = g_list_append(pList, h);
  h->real_name = strdup(name);
  count++;

  MK_FID(res,MY_VOL,MY_VNODE,(long)h);
  DEBUG_PRINT( "new vfid created at %p (file <%s>)\n",h,h->real_name );
  return res;
}

#if 0
void
dump_vfid_list(void) 
{
  GList* pWalk;
  for ( pWalk = pList; pWalk; pWalk = g_list_next(pWalk) ) {
  	struct v_list* v = (struct v_list*) pWalk->data;
  	printf("list element <%s>\n",v->real_name);
  }
}
#endif

void
delete_name( const char * real_name ) {
  GList *pWalk;
  /* at this point, the paranoid would check for a dirty file and write it out */
  for( pWalk = pList; pWalk; pWalk = g_list_next(pWalk) ) {
    if (!strcmp( ((struct v_list*)pWalk->data)->real_name, real_name)) {
      struct v_list* v = (struct v_list*) (pWalk->data);
      free(v->real_name);
      if (v->local_name) free(v->local_name);
      pList = g_list_remove( pList, pWalk->data );
      free(pWalk->data);
      count--;
      return;
    }
  }
  printf( "failure trying to delete the file %s\n", real_name );
}

inline int
CodaToVfsFlags( int flags )
{
  return ((flags & (C_O_READ | C_O_WRITE)) == (C_O_READ | C_O_WRITE)) ? O_RDWR :
	   (((flags & (C_O_READ | C_O_WRITE)) == C_O_WRITE) ? O_WRONLY : O_RDONLY) |
	((flags & C_O_TRUNC) ? O_TRUNC : 0) |
	((flags & C_O_CREAT) ? O_CREAT : 0) |
	((flags & C_O_EXCL) ? O_EXCL : 0);
}

/*********************local copy management ********************************/
inline char *
get_local_copy( ViceFid id, int flags )
{
  struct v_list* v = (struct v_list*)id.Unique;
  /* I would normally put an assert here, but look_name() was called already */
  /* truncated files do not need an ftp read first; this code cluster handles it */
  v->is_created = 0;
  if (flags & C_O_WRITE)
    v->has_changed = 1;
  if (flags & C_O_TRUNC) {
    v->is_trunc = 1;
    v->local_name = tempnam( NULL, "podfu" );
    close(open(v->local_name, CodaToVfsFlags(flags),0666)); /* touch the file */
    return v->local_name;
  } else
    return v->local_name = mc_getlocalcopy(v->real_name); /* fetch from other end */
}

void
write_dir_entry( int handle, char *name )
{
  struct venus_dirent ent;
  int len;
  static int ino = MY_INODE;
  
  ent.d_fileno = ino++;
  ent.d_type = CDT_REG; /* the kernel does not use this */
  
  ent.d_namlen = len = strlen(name);
  strcpy(ent.d_name, name);
  len += ((char *) &ent.d_name - (char *) &ent);
  ent.d_reclen = len;
  if (write(handle, &ent, len)<0)
    printf( "error writing dirent (%m)\n" );   /* FIXME: we should propagate error */
  DEBUG_PRINT( "[%s]", name );
}

char *
get_dir_copy( ViceFid id )
{
  struct v_list* v = (struct v_list*) id.Unique;
  DIR *dir = mc_opendir(v->real_name);
  char *cache;
  int handle;
  struct dirent *ent1;
  char zeros[128] = {0,0, };

  if (!dir)
    return NULL;
  v->is_dir = 1;
  DEBUG_PRINT( "converting dir to file " );
  cache = tempnam (NULL, "dir");
  handle = open (cache, O_WRONLY | O_CREAT | O_EXCL, 0600);
  if (handle == -1) {
    printf("can not open temp file for directory list (%m)\n" );
    mc_closedir(dir);
    free(cache);
    return NULL;
  }
  DEBUG_PRINT( "%s:", cache );

  /* copy a list file names out to a file */
  while ((ent1 = mc_readdir(dir)))
     write_dir_entry(handle, ent1->d_name);
  write(handle, zeros, 2);
  DEBUG_PRINT(" ");
  close(handle);
  mc_closedir(dir);
  return v->local_name = cache;
}

void
unget_local_copy( ViceFid id )
{
  struct v_list* v = (struct v_list*) id.Unique;
  if (v->local_name) {
    if (v->has_changed) DEBUG_PRINT( "saving the file %s ", v->local_name);
        /* NOTE: if we give mc_ungetlocalcopy() the exact correct localfile name,
         it will not free() the 2nd parameter string */
    if (v->is_dir) {
      unlink( v->local_name );
    } else if (v->is_trunc) {
              /* mc_filecopy() */
      int hw = mc_open(v->real_name, O_WRONLY | O_CREAT | O_TRUNC, 0666);
      int hr = open(v->local_name, O_RDONLY);
      if (hw>=0 && hr>=0) {
        char*p = g_new(char,0x4000);
        int n = 0x4000;
        while (n>0) {
          n = read(hr,p,n);
          if (n>0) n = mc_write(hw,p,n);
        }
        mc_close(hw);
        close(hr);
        unlink(v->local_name); free(v->local_name);
        g_free(p);
        v->is_trunc = 0;
      }
    } else
        /* will free the local_name */
      mc_ungetlocalcopy(v->real_name, v->local_name, v->has_changed);
    v->local_name = 0;
    v->has_changed = 0;
  } else {
    DEBUG_PRINT( "<empty file name on a close>" );
  }
}

/****************** attribute mangling *************************************/
inline int
st2type(struct stat *s)
{
  if (S_ISDIR(s->st_mode))
    return C_VDIR;
  if (S_ISREG(s->st_mode))
    return C_VREG;
  if (S_ISBLK(s->st_mode))
    return C_VBLK;
  if (S_ISCHR(s->st_mode))
    return C_VCHR;
  if (S_ISLNK(s->st_mode))
    return C_VLNK;
  if (S_ISSOCK(s->st_mode))
    return C_VSOCK;
  if (S_ISFIFO(s->st_mode))
    return C_VFIFO;
  printf( "Unknown type in st2type (%o)\n", s->st_mode );
  return C_VNON;
}

void
st2attr(struct stat *s, struct coda_vattr *a)
{
  static int id = 0;

  bzero(a, sizeof(struct coda_vattr));
  a->va_type = st2type(s);
#define COPY(x) a->va_##x = s->st_##x;
  COPY(mode);
  DEBUG_PRINT( "(mode = %o)", s->st_mode );
  COPY(nlink);
  COPY(uid);
  COPY(gid);
  a->va_fileid = id++;
  COPY(size);
  a->va_blocksize = 1024; /* s->st_blksize; */
  a->va_atime.tv_nsec = a->va_ctime.tv_nsec = a->va_mtime.tv_nsec = 0;
  a->va_atime.tv_sec = s->st_atime;
  a->va_mtime.tv_sec = s->st_mtime;
  a->va_ctime.tv_sec = s->st_ctime;
  a->va_gen = 0;
  a->va_flags = 0;

  a->va_bytes = s->st_blocks * s->st_blksize;
  a->va_filerev = 0;

}

/* Poor protection against deadlocks when podfuk is mounted on
   /overlay (right protection is VFS_NO_LOCALHASH, which is
   unfortunately in new midnights, only). 

   In fact, "new midnight" currently means my own tree at my home
   machine :-) */
int 
legal_name(char *name)
{
  if (!strncmp (name, MOUNT_POINT_STRING, strlen(MOUNT_POINT_STRING))
        || (strchr(name, '#') && !vfs_rosplit(name)))
    return 0;
  return 1;
}

int
main(int argc, char** argv)
{
  char buf[2048];
  time_t time_last_msg = (time_t) 0x7fffffff;

  if (argc>1) {
    printf("%s: a virtual file system extention to Linux\n",argv[0]);
    printf("   version " VERSION_STRING "\n");
    printf("   usage: modprobe coda; %s; mount /dev/cfs0 /mnt/.mcvfs -tcoda\n",argv[0]);
    exit(1);
  }
    
  setvbuf(stdout, NULL, _IONBF, 0);
  cfs = open( "/dev/cfs0", O_RDWR );
  if (cfs == -1) {
    printf( "Error opening cfs0: %m\n" );
    exit(1);
  } 
#ifndef NODAEMON
  chdir("/");
  if (fork()) {
    exit(0);
  }
  setsid(); /* to guarantee effect, the man page says this should be performed after
	       a fork, as the child... */
  DEBUG_PRINT( "podfuk spawned as %d, ", getpid() );
#endif
  DEBUG_PRINT( "Opened cfs0\n" );
#ifdef USE_MONITOR
  vfs_set_callback( CALL_INFO, progress_func );
  monitor_handle = open("/var/tmp/podfuk.pipe", O_RDWR | O_NONBLOCK);
  if (monitor_handle==-1)
    DEBUG_PRINT( "failed to open pipe to monitor\n" );
#endif
  load_profile( "/etc/podfukrc", set_global_var );
  mc_vfs_init();
  
  signal(SIGTERM, signal_handler); /* graceful shutdown */
  signal(SIGINT,  signal_handler);
  signal(SIGPIPE, SIG_IGN);     /* pipe faults are benign */

  vfs_flags |= FL_ALWAYS_MAGIC;
#ifdef FL_NO_LOCALHASH
  vfs_flags |= FL_NO_LOCALHASH;	/* Avoid local deadlock when someone accesses /#ahoj#utar */
#else
#warning Sorry, your midnight is old. It may deadlock under some uses.
#endif

  while(1) {
      /* NOTE: both the inbuf/req and the outbuf/rep must be bigger than
       * just the union given in coda.h
       */
    char in_buf[sizeof(union inputArgs) + CODA_MAXNAMLEN];
    union inputArgs *req = (union inputArgs*) &in_buf[0];
    char out_buf[sizeof(union outputArgs) + CODA_MAXNAMLEN];
    union outputArgs *rep = (union outputArgs*) &out_buf[0];
    struct stat st;
    int msg;
    int size;
    char *name;
    fd_set rfds;
    struct timeval tv;
    int retval;

    /* Watch stdin (fd 0) to see when it has input. */
    FD_ZERO(&rfds);
    FD_SET(cfs, &rfds);

    /* Wait up to five seconds. */
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    retval = select(cfs+1, &rfds, NULL, NULL, &tv);
    /* onError */
    if (retval == -1) {
      printf( "select failed: %m\n" );
      exit(1);
    }
    /* onTimeout */
    if (retval == 0) {
        if (time(NULL)-time_last_msg > FLUSH_TIME) {
          flush_VFids();
          time_last_msg = (time_t) 0x7fffffff; /* only flush once */
        }
    }
    
    vfs_timeout_handler();
    /* Don't rely on the value of tv now! */
    
    msg = read(cfs, req, sizeof(*req) + 256);
    if (msg == -1)
        continue;

    DEBUG_PRINT( "got %3.3d byte command: opcode = %2.2ld ", msg, req->ih.opcode );
    //    printf( " (un=%d,pid=%d,pgid=%d) ", req.ih.unique, req.ih.pid, req.ih.pgid );
    //    printf( " (uid=%d,euid=%d,suid=%d,fsuid=%d) ",req.ih.cred.cr_uid,req.ih.cred.cr_euid,req.ih.cred.cr_suid,req.ih.cred.cr_fsuid );
    setfsgid (req->ih.cred.cr_fsgid); vfs_gid = req->ih.cred.cr_fsgid;
    setfsuid (req->ih.cred.cr_fsuid); vfs_uid = req->ih.cred.cr_fsuid;
    rep->oh.opcode = req->ih.opcode;
    rep->oh.unique = req->ih.unique;
    rep->oh.result = ENOSYS;
    size = sizeof(rep->oh);

#define CMD(x) rep->oh.result = 0; size = sizeof(rep->coda_##x); DEBUG_PRINT( "%.10s",#x );
#define CMD_NOREP(x) rep->oh.result = 0; DEBUG_PRINT( "%.10s",#x );
//#define DUMP(x) DEBUG_PRINT( "(%x/%x/%x:%s)", req->coda_##x.VFid.Volume, req->coda_##x.VFid.Vnode, req->coda_##x.VFid.Unique, look_name(req->coda_##x.VFid));
#define DUMP(x) DEBUG_PRINT( "\n\tbase:%s\n\t", look_name(req->coda_##x.VFid));
#define DUMP_NAME(x) DEBUG_PRINT( "\n\tbase:%s\tname:%s\n\t", look_name(req->coda_##x.VFid), (char*)req + req->coda_##x.name);
#define STAT(x) if (!legal_name(x)) {rep->oh.result = ENOENT; break; } else if (mc_lstat(x, &st) == -1) { rep->oh.result = errno; DEBUG_PRINT( "file %s probably does not exist.",x ); break; }
#define BUILD_NAME(x) name = ((char*) req) + req->coda_##x.name; sprintf( buf, "%s/%s", look_name(req->coda_##x.VFid), name);
#define GET_VFID_name(x) if (NULL==(name=look_name(req->coda_##x.VFid))) {rep->oh.result = ENOENT;  break;}
    
    switch (req->ih.opcode) {
    case CODA_ROOT:
      CMD(root);
      DEBUG_PRINT( ": " );
      rep->coda_root.VFid = alloc_vfid( "/" );
      v_usage(rep->coda_root.VFid) = 1;
      break;

      /********************* file props & access *******************/
    case CODA_GETATTR:
      CMD(getattr);
      DUMP(getattr);
      GET_VFID_name(getattr);
      if (!v_created(req->coda_getattr.VFid)) {
        STAT(name); /* fills in st */
      } else
        st = generic_stat;
      st2attr( &st, &rep->coda_getattr.attr );
      break;
    case CODA_SETATTR:
      CMD_NOREP(setattr);
      DUMP(setattr);
      GET_VFID_name(setattr);
      DEBUG_PRINT( " try to set mode to %o, mdate to %lx and owner to %d: ", req->coda_setattr.attr.va_mode, req->coda_setattr.attr.va_mtime.tv_sec, req->coda_setattr.attr.va_uid );
      if ( req->coda_setattr.attr.va_mode != (u_short) -1)
        mc_chmod( name, req->coda_setattr.attr.va_mode );
      /* cannot change owner, group or dates .......*/
      break;
    case CODA_ACCESS:
        /* this always returns TRUE; this seems to be ok with current CODA design */
      CMD_NOREP(access);
      DUMP(access); DEBUG_PRINT( "flags:%x ", req->coda_access.flags );
      GET_VFID_name(access);
      break;
    case CODA_LOOKUP: /* !! ttd: search for existing VFid's instead of making many new ones */
      CMD(lookup);
      DUMP_NAME(lookup);
      GET_VFID_name(lookup);
      DEBUG_PRINT( "flags=%d: ", req->coda_lookup.flags );
      BUILD_NAME(lookup);
      DEBUG_PRINT( "(stat: %s)", buf );
      STAT(buf); /* fills in st */
      rep->coda_lookup.VFid = alloc_vfid(buf);
      rep->coda_lookup.vtype = st2type(&st);
      break;
    case CODA_READLINK:
      CMD(readlink);
      DUMP(readlink);
      GET_VFID_name(readlink);
      /* put the string at the end of the structure
       * the reply packet must be smaller than the request packet
       */
      retval = mc_readlink(name,
                           (char*)&rep + sizeof(rep->coda_readlink),
                           msg - sizeof(rep->coda_readlink));
      if (retval<=0) {
        rep->oh.result = ENOENT;
      } else {
        rep->coda_readlink.count = retval; /* == strlen(sym_link_string) */
        rep->coda_readlink.data = (void*) sizeof(rep->coda_readlink);
        size += retval; /* the structure is longer by this amount */
      }
      break;

      /************************ open, close, create & mkdir ******************/
    case CODA_OPEN:
      CMD(open);
      DUMP(open); DEBUG_PRINT( "flags:%#x ", req->coda_open.flags );
      GET_VFID_name(open);
      if (!v_created(req->coda_open.VFid)) {
        STAT(name);
      } else
        st = generic_stat;
      if (S_ISDIR(st.st_mode)) {
	DEBUG_PRINT( "[open request on a directory]" );
	name = get_dir_copy(req->coda_open.VFid);
      } else {
#ifdef USE_MONITOR
        if (monitor_handle>=0) {
          char* pName;
          /* feeble attempt to hide the password */
          if (!memcmp(name,"/#ftp:",6))
            pName = strchr( name+1,'/' );
          if (!pName) pName = name;
          sprintf(monitor_buf,"NAME %s",pName);
          write(monitor_handle, monitor_buf, strlen(monitor_buf+1));
        }
#endif
        name = get_local_copy(req->coda_open.VFid,req->coda_open.flags);
      }
      if (!name || (stat(name, &st)==-1)) {
	rep->oh.result = errno;
	break;
      }
      v_usage(req->coda_open.VFid) += 1;
      rep->coda_open.dev = st.st_dev;
      rep->coda_open.inode = st.st_ino;
      break;
    case CODA_CLOSE:
      CMD_NOREP(close);
      DUMP(close);
      GET_VFID_name(close);
      unget_local_copy(req->coda_close.VFid);
      DEBUG_PRINT( " the current VFid list has %d elements\n", g_list_length( pList ) );
      //	dump_vfid_list();
#ifdef USE_MONITOR
      if (monitor_handle>=0) {
          write(monitor_handle,"DONE",5);
      }
#endif
      v_usage(req->coda_close.VFid) -= 1;
      break;
    case CODA_CREATE:
      CMD(create);
      DUMP_NAME(create);
      GET_VFID_name(create);
      BUILD_NAME(create);
      DEBUG_PRINT( "(create_name: %s)", buf );
      rep->coda_create.VFid = alloc_vfid(buf);
      v_created(rep->coda_create.VFid) = 1;
      rep->coda_create.attr = req->coda_create.attr;
      break;
    case CODA_MKDIR:
      CMD(mkdir);
      DUMP_NAME(mkdir);
      GET_VFID_name(mkdir);
      BUILD_NAME(mkdir);
      DEBUG_PRINT( "(mkdir: %s)", buf );
      if ( mc_mkdir( buf, req->coda_mkdir.attr.va_mode ) == 0) {
        rep->coda_mkdir.VFid = alloc_vfid(buf);
        rep->coda_mkdir.attr = req->coda_mkdir.attr;
      } else
        rep->oh.result = errno;
      break;

      /********************* deletions & rename ******************/
    case CODA_RMDIR:
      CMD_NOREP(rmdir);
      DUMP_NAME(rmdir);
      GET_VFID_name(rmdir);
      BUILD_NAME(rmdir);
      DEBUG_PRINT( "(rmdir: %s)", buf );
      if ( mc_rmdir( buf ) == 0) {
        rep->oh.result = 0;
        delete_name( buf );
      } else
        rep->oh.result = errno;
      break;
    case CODA_REMOVE:
      CMD_NOREP(remove);
      DUMP_NAME(remove);
      GET_VFID_name(remove);
      BUILD_NAME(remove);
      DEBUG_PRINT( "(remove: %s)", buf );
      if ( mc_unlink( buf ) == 0) {
        rep->oh.result = 0;
        delete_name( buf );
      } else
        rep->oh.result = errno;
      break;
    case CODA_RENAME:
      CMD_NOREP(rename);
      {
        char buf2[256];
        name = ((char *) req) + req->coda_rename.srcname;
        if (!name) {
          rep->oh.result = ENOENT;
          break;
        }
        sprintf( buf, "%s/%s", look_name(req->coda_rename.sourceFid), name );
        name = ((char *) req) + req->coda_rename.destname;
        sprintf( buf2, "%s/%s", look_name(req->coda_rename.destFid), name );
        DEBUG_PRINT( "src name=%s, dest name %s: ", buf, buf2 );
        if ( mc_rename( buf, buf2 ) == 0) {
          rep->oh.result = 0;
          delete_name( buf );
        } else
          rep->oh.result = errno;
      }
      break;

    default:
      DEBUG_PRINT("unimplemented coda call");
      break;
    }

    DEBUG_PRINT( "returning %s (%ld), %d bytes\n",
	rep->oh.result ? "FAILURE" : "SUCCESS",
    	rep->oh.result, size );
    msg = write(cfs, rep, size);
    fflush(stdout);

    setegid (0); vfs_gid = 0;
    seteuid (0); vfs_uid = 0;

    time_last_msg = time(NULL);
  } /* while */
}

/* Leave this in place - this is here so that code still is valid Makefile. */
#if 0
endif
#endif

