/* Virtual File System: Midnight Commander file system.
   This has nothing to do with cryptography.
   
   Copyright (C) 1995, 1996, 1997 The Free Software Foundation

   Written by Miguel de Icaza
              Andrej Borsenkow
	      Norbert Warmuth
	      Paul Sheer
   
   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; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <config.h>
#include "net-includes.h"
#include "diffie/diffie-socket.h"
#include "diffie/z-socket.h"
#include "mem.h"
#include "vfs.h"
#include "mcfs.h"
#include "tcputil.h"
#include "util.h"
#ifndef STANDALONE_VFS
#include "../src/dialog.h"
#endif
#include "diffie/compat.h"
#include "mad.h"

#define MCFS_MAX_CONNECTIONS 32
#define DEFAULT_PROMPT_CHARS "#$>"

#ifdef HAVE_MAD
#define exit(x) { mad_finalize(__FILE__,__LINE__); exit(x); }
#endif

static int mcfs_open_connections = 0;
struct _mcfs_connection {
    char *host;
    char *user;
    char *home;
    int  sock;
    int  port;
    int  version;
} mcfs_connections [MCFS_MAX_CONNECTIONS];

typedef struct _mcfs_connection mcfs_connection;

typedef struct { int handle; mcfs_connection *conn; } mcfs_handle;

static int mcfs_errno_var;
static mcfs_connection *current_dir_connection;

char *mcfs_current_dir = 0;

/* Extract the hostname and username from the path */
/* path is in the form: hostname:user/remote-dir */
#ifdef HAVE_MAD
char *mad_mcfs_get_host_and_username (char *path, char **host, char **user,
				  int *port, char **pass, char *file, int line)
#define mcfs_get_host_and_username(p,h,u,o,a) \
	mad_mcfs_get_host_and_username(p,h,u,o,a,__FILE__,__LINE__)
#else
char *mcfs_get_host_and_username (char *path, char **host, char **user,
				  int *port, char **pass)
#endif
{
#ifdef HAVE_MAD
    return mad_get_host_and_username (path, host, user, port, 0, 0, pass, file, line);
#else
    return get_host_and_username (path, host, user, port, 0, 0, pass);
#endif
}

void mcfs_fill_names (void (*func)(char *))
{
    int i;
    char *name;
    
    for (i = 0; i < MCFS_MAX_CONNECTIONS; i++){
	if (mcfs_connections [i].host == 0)
	    continue;
	name = copy_strings ("mc:", mcfs_connections [i].user,
			     "@",   mcfs_connections [i].host, 0);
	(*func) (name);
	free (name);
    }
}

void mcfs_free_bucket (int bucket)
{
    free (mcfs_connections [bucket].host);
    free (mcfs_connections [bucket].user);
    if (mcfs_connections [bucket].home)
	free (mcfs_connections [bucket].home);

    /* Set all the fields to zero */
    mcfs_connections [bucket].host =
	mcfs_connections [bucket].user =
	    mcfs_connections [bucket].home = 0;
    mcfs_connections [bucket].sock =
	mcfs_connections [bucket].version = 0;
}

/* FIXME: This part should go to another c module, perhaps tcp.c */
int mcfs_invalidate_socket (int);

void tcp_invalidate_socket (int sock)
{
     mcfs_invalidate_socket (sock);
}
/* FIXME end: 'cause it is used not only by mcfs */

int mcfs_invalidate_socket (int sock)
{
    int i, j = -1;
    extern int mc_chdir (char *);
    
    for (i = 0; i < MCFS_MAX_CONNECTIONS; i++)
	if (mcfs_connections [i].sock == sock) {
	    mcfs_free_bucket (i);
	    j = 0;
	}

    if (j == -1)
        return -1; /* It was not our sock */
    /* Break from any possible loop */
    mc_chdir (PATH_SEP_STR);
    return 0;
}

extern char *last_current_dir;

/* This routine checks the server RPC version and logs the user in */
static int xmcfs_login_server (int my_socket, char *host, char *user, int port,
			      int port_autodetected, char *netrcpass,
			      int *version, int cmd)
{
    int result;
    char *pass;
    
    /* Send the version number */
    rpc_send (my_socket, RPC_INT, *version, RPC_END);
    if (0 == rpc_get (my_socket, RPC_INT, &result, RPC_END))
	return 0;
    
    if (result != MC_VERSION_OK){
	message_1s (1, " MCFS ", " Outdated protocol version of server, please upgrade ");
	return 0;
    }

    rpc_send (my_socket, RPC_INT, cmd, RPC_STRING, last_current_dir,
        RPC_STRING, user, RPC_END);
    
    if (0 == rpc_get  (my_socket, RPC_INT, &result, RPC_END))
	return 0;
    
    if (result == MC_NEED_PASSWORD){
	if (port > 1024 && port_autodetected){
	    int v;
#ifndef STANDALONE_VFS
	    v = query_dialog (" Warning ",
	        " The remote server is not running on a system port \n"
		" you need a password to log in, but the information may \n"
   	        " not be safe on the remote side.  Continue? \n", 3, 2,
			      " Yes ",  " No ");
#else
	    v = 0;
#endif
	    if (v == 1){
		shutdown (my_socket, 2);
		return 0;
	    }
	}
	if (netrcpass != NULL)
	    pass = (char *) strdup (netrcpass);
	else
	    pass = input_dialog (" MCFS Password required ", "Password:", "", host, user);
	if (!pass){
	    rpc_send (my_socket, RPC_INT, MC_QUIT, RPC_END);
	    shutdown (my_socket, 2);
	    return 0;
	}
	rpc_send (my_socket, RPC_INT, MC_PASS, RPC_STRING, pass, RPC_END);

	wipe_password (pass);

	if (0 == rpc_get (my_socket, RPC_INT, &result, RPC_END))
	    return 0;

	if (result != MC_LOGINOK){
	    message_1s (1, " MCFS ", " Invalid password ");
#ifdef EPERM
	    errno = EPERM;
#endif
	    rpc_send (my_socket, RPC_INT, MC_QUIT, RPC_END);
	    shutdown (my_socket, 2);
	    return 0;
	}
    }
    return my_socket;
}

static int mcfs_login_server (int my_socket, char *host, char *user, int port,
			      int port_autodetected, char *netrcpass,
			      int *version)
{
    return xmcfs_login_server (my_socket, host, user, port, port_autodetected,
			      netrcpass, version, MC_LOGIN);
}

int mcfs_open_tcp_link (char *host, char *user, int *port, char *netrcpass, int *version)
{
    int my_socket;
    int old_port = *port;

    my_socket = open_tcp_link (host, port, version, " MCfs ");
    if (my_socket <= 0) {
	mcfs_errno_var = errno;
	return 0;
    }
    
    /* We got the connection to the server, verify if the server
       implements our version of the RPC mechanism and then login
       the user.
    */
  try_again:
    my_socket = mcfs_login_server (my_socket, host, user, *port, old_port == 0,
			      netrcpass, version);
    if (my_socket <= 0 && *version == 3) {
	my_socket = open_tcp_link (host, port, version, " MCfs ");
	*version = 2;
	goto try_again;
    }
    if (my_socket <= 0) {
	mcfs_errno_var = errno;
	return 0;
    }
    return my_socket;
}

static int mcfs_get_free_bucket_init = 1;
static mcfs_connection *mcfs_get_free_bucket (void)
{
    int i;
    
    if (mcfs_get_free_bucket_init) {
        mcfs_get_free_bucket_init = 0;
        for (i = 0; i < MCFS_MAX_CONNECTIONS; i++)
	    mcfs_connections [i].host = 0;
    }
    for (i = 0; i < MCFS_MAX_CONNECTIONS; i++){
	if (!mcfs_connections [i].host)
	    return &mcfs_connections [i];
    }
    /* This can't happend, since we have checked for max connections before */
    fprintf (stderr, "Internal error: mcfs_get_free_bucket");
    return 0;
}

/* This routine keeps track of open connections */
/* Returns a connected socket to host */
static mcfs_connection *mcfs_open_link (char *host, char *user, int *port, char *netrcpass)
{
    int i, sock, version;
    mcfs_connection *bucket;

    /* Is the link actually open? */
    if (mcfs_get_free_bucket_init) {
        mcfs_get_free_bucket_init = 0;
        for (i = 0; i < MCFS_MAX_CONNECTIONS; i++)
	    mcfs_connections [i].host = 0;
    } else for (i = 0; i < MCFS_MAX_CONNECTIONS; i++){
	if (!mcfs_connections [i].host)
	    continue;
	if ((strcmp (host, mcfs_connections [i].host) == 0) &&
	    (strcmp (user, mcfs_connections [i].user) == 0))
	    return &mcfs_connections [i];
    }
    if (mcfs_open_connections == MCFS_MAX_CONNECTIONS){
	message_1s (1, " Error ", " Too many open connections ");
	return 0;
    }

    if (!(sock = mcfs_open_tcp_link (host, user, port, netrcpass, &version)))
	return 0;
    
    bucket = mcfs_get_free_bucket ();
    mcfs_open_connections++;
    bucket->host    = (char *) strdup (host);
    bucket->user    = (char *) strdup (user);
    bucket->home    = 0;
    bucket->port    = *port;
    bucket->sock    = sock;
    bucket->version = version;

    return bucket;
}

static int is_error (int result, int errno_num)
{
    if (!(result == -1))
	return mcfs_errno_var = 0;
    else 
	mcfs_errno_var = errno_num;
    return 1;
}

#ifdef B19200

static int get_speed_as_int (speed_t speed) 
{
    switch(speed) {
	case B0: return 0;
	case B50: return 50;
	case B75: return 75;
	case B110: return 110;
	case B134: return 134;
	case B150: return 150;
	case B200: return 200;
	case B300: return 300;
	case B600: return 600;
	case B1200: return 1200;
	case B1800: return 1800;
	case B2400: return 2400;
	case B4800: return 4800;
	case B9600: return 9600;
	case B19200: return 19200;
	case B38400: return 38400;
	case B57600: return 57600;
	case B115200: return 115200;
#ifdef B230400
	case B230400: return 230400;
#endif
#ifdef B460800
	case B460800: return 460800;
#endif
    }
    return 9600;
}

#endif
int option_user_eight_bits = 0;

#ifdef B19200
static struct termios original_tios;
#endif

#if (RETSIGTYPE==void)
#define handler_return return
#else
#define handler_return return 1
#endif

#if 0
#ifndef SIGUSR1
#define SIGUSR1 30
#endif
#endif

#ifdef SIGPIPE
RETSIGTYPE pipe_died (int unused)
{
    signal (SIGCHLD, SIG_DFL);
    signal (SIGPIPE, SIG_IGN);
    signal (SIGURG, SIG_IGN);
    fprintf (stderr, "Connection closed.\r\n");
#ifdef B19200
    tcsetattr (0, TCSADRAIN, &original_tios);
#endif
    exit (0);
    handler_return;
}
#endif

RETSIGTYPE exit_died (int unused)
{
    exit (1);
    handler_return;
}

enum {
    SHELL_WINSZ = 256,
    SHELL_DATA
};

#ifdef TIOCGWINSZ

static int send_window_size = 1;
static int write_socket;

RETSIGTYPE window_size (int unused)
{
    int cmd = SHELL_WINSZ;
    struct winsize w;
    signal (SIGWINCH, SIG_IGN);
    if (send_window_size) {
	ioctl (0, TIOCGWINSZ, &w);
	rpc_send (write_socket, RPC_INT, cmd, RPC_INT, (int) w.ws_row,
	    RPC_INT, (int) w.ws_col, RPC_INT, (int) w.ws_xpixel, RPC_INT,
		  (int) w.ws_ypixel, RPC_END);
    }
    signal (SIGWINCH, window_size);
    handler_return;
}

#endif				/* TIOCGWINSZ */

char *prompt_chars = DEFAULT_PROMPT_CHARS;
FILE *my_stdout;

#define fprintf1(f,x)
#define fprintf2(f,x,y)
#define fprintf3(f,x,y,z)

/* this was written using rlogin as a tutorial. here there is no
   such thing as out out-of-band data */
int mcfs_open_login (char *url, int test_login)
{
    int wait_for_prompt;
#ifdef B19200
    struct termios tios;
#endif
    int speed = 0;
    int my_socket;
    char *host, *user, *pass, *path;
    int port, version;
    pid_t pid;
    pid = getpid ();

    my_stdout = (FILE *) fdopen ((int) dup (1), "w");

    if (strncmp (url, "mc:" PATH_SEP_STR PATH_SEP_STR, 5))
	return 1;
    url += 5;
    path = get_host_and_username (url, &host, &user, &port, 0, 0, &pass);
    if (!strchr (url, PATH_SEP)) {
	free (path);
	path = (char *) strdup ("");
    } else if (!strcmp (path, PATH_SEP_STR "~")) {
	free (path);
	path = (char *) strdup ("");
    }
    my_socket = open_tcp_link (host, &port, &version, " MCfs ");
    if (my_socket <= 0)
	return 1;
    if (version == 3)		/* FIXME */
	version = 2;

    if (test_login)
	return (xmcfs_login_server (my_socket, host, user, port, 1, 0, &version, MC_LOGIN) <= 0);

    my_socket = xmcfs_login_server (my_socket, host, user, port, 1, 0, &version, MC_SHELL);

    if (my_socket <= 0)
	return 1;
#ifdef B19200
    memset (&tios, 0, sizeof (tios));
    if (tcgetattr (0, &tios) == 0) {
	speed_t s = cfgetispeed (&tios);
	speed = get_speed_as_int (s);
    }
    memcpy (&original_tios, &tios, sizeof (original_tios));
#endif

    rpc_send (my_socket, RPC_STRING, getenv ("TERM"),
	      RPC_INT, speed, RPC_STRING, path, RPC_END);

#if defined(IP_TOS) && 0
    on = IPTOS_LOWDELAY;
    setsockopt (my_socket, IPPROTO_IP, IP_TOS, (char *) &on, sizeof (int));
#endif

    signal (SIGINT, SIG_IGN);
#ifdef SIGTSTP
    signal (SIGTSTP, SIG_IGN);
#endif
#ifdef SIGURG
    signal (SIGURG, SIG_IGN);
#endif
#ifdef SIGHUP
    signal (SIGHUP, exit_died);
#endif
#ifdef SIGQUIT
    signal (SIGQUIT, exit_died);
#endif
#ifdef SIGPIPE
    signal (SIGPIPE, pipe_died);
#endif

#ifdef TIOCGWINSZ
    write_socket = my_socket;	/* for window resize signal handler */
#endif

#ifdef TIOCGWINSZ
    signal (SIGWINCH, window_size);
#else
#ifdef SIGWINCH
    signal (SIGWINCH, SIG_DFL);
#endif
#endif
#if 0
    signal (SIGUSR1, send_directory);
#endif
#ifdef SIGPIPE
    signal (SIGPIPE, exit_died);
#endif
#ifdef SIGTTOU
    signal (SIGTTOU, SIG_IGN);
#endif
#ifdef SIOCSPGRP
    ioctl (my_socket, SIOCSPGRP, &pid);
#else
#ifdef F_SETOWN
    fcntl (my_socket, F_SETOWN, pid);	/* CHECK???? */
#endif
#endif

#ifdef TIOCGWINSZ
    window_size (0);
#endif

#ifdef B19200
#ifdef OCRNL
    tios.c_oflag &= ~(ONLCR | OCRNL);
#else
    tios.c_oflag &= ~ONLCR;
#endif
    tios.c_lflag &= ~(ECHO | ICANON | ISIG);
    tios.c_iflag &= ~(ICRNL);
    tios.c_cc[VTIME] = 1;
    tios.c_cc[VMIN] = 1;
    if (option_user_eight_bits)
	tios.c_iflag &= ~(ISTRIP);
#if defined(TABDLY) && defined(TAB3)
    if ((tios.c_oflag & TABDLY) == TAB3)
	tios.c_oflag &= ~TAB3;
#endif
/* disable interpretation of ^S: */
    tios.c_iflag &= ~IXON;
/* no idea if we need these #ifdef's but they look cool */
#ifdef VDISCARD
    tios.c_cc[VDISCARD] = 255;
#endif
#ifdef VEOL2
    tios.c_cc[VEOL2] = 255;
#endif
#ifdef VEOL
    tios.c_cc[VEOL] = 255;
#endif
#ifdef VLNEXT
    tios.c_cc[VLNEXT] = 255;
#endif
#ifdef VREPRINT
    tios.c_cc[VREPRINT] = 255;
#endif
#ifdef VSUSP
    tios.c_cc[VSUSP] = 255;
#endif
#ifdef VWERASE
    tios.c_cc[VWERASE] = 255;
#endif
    tcsetattr (0, TCSADRAIN, &tios);
#endif
    wait_for_prompt = *path;

#if 0
#define debug(x,y,d) printf ("%s: %s: %d\n", (x), (y), (d))
#else
#define debug(x,y,d)
#endif

#undef MAX
#define MAX(x,y) ((x) > (y) ? (x) : (y))
#undef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define WOULD_BLOCK(c) { if ((c) < 0 && errno == EWOULDBLOCK) (c) = 0; if ((c) < 0) break; }

#undef BUFFER_SIZE
#define BUFFER_SIZE 1024
    {
	int remaining_in = 0;
	char remote_buf[BUFFER_SIZE + 1], local_buf[BUFFER_SIZE + 1];
	int remote_avail = 0, local_avail = 0;
	int remote_written = 0, local_written = 0;
	int yes = 1, r;

	ioctl (0, FIONBIO, &yes);
	ioctl (1, FIONBIO, &yes);

	for (;;) {
	    int cmd;
	    int n = 0;
	    fd_set writing, reading;
	    FD_ZERO (&writing);
	    FD_ZERO (&reading);
/* is there stuff waiting to be written to local ? */
	    if (remote_avail > remote_written) {
		FD_SET (1, &writing);
		n = MAX (1, n);
	    }
/* is there stuff waiting to be written to remote ? */
	    if (local_avail > local_written) {
		FD_SET (my_socket, &writing);
		n = MAX (my_socket, n);
	    }
/* is there buffer space to read from local ? */
	    if (local_avail < BUFFER_SIZE) {
		FD_SET (0, &reading);
		n = MAX (0, n);
	    }
/* is there buffer space to read from remote ? */
	    if (remote_avail < BUFFER_SIZE) {
		FD_SET (my_socket, &reading);
		n = MAX (my_socket, n);
	    }
	    debug ("select", "", n);
	    do {
		r = select (n + 1, &reading, &writing, NULL, NULL);
	    } while (r == -1 && errno == EINTR);
	    if (r == -1)
		break;
	    debug ("select", "done", 0);
	    if (FD_ISSET (0, &reading)) {
		int c;
		debug ("reading", "0", BUFFER_SIZE - local_avail);
		c = read (0, local_buf + local_avail, BUFFER_SIZE - local_avail);
		if (!c)
		    break;
		debug ("read   ", "0", c);
		WOULD_BLOCK (c);
		local_avail += c;
		if (c)
		    goto try_write_remote;
	    }
	    if (FD_ISSET (my_socket, &writing)) {
		int c;
	      try_write_remote:
		cmd = SHELL_DATA;
		debug ("writing", "my_socket", local_avail - local_written);
#ifdef TIOCGWINSZ
		send_window_size = 0;
#endif
		c = local_avail - local_written;
		if (c == 1) {
		    if (!rpc_send (my_socket, RPC_INT, (int) ((unsigned char) local_buf[local_written]), RPC_END))
			break;
		} else if (c > 0) {
		    if (!rpc_send (my_socket, RPC_INT, cmd, \
				   RPC_INT, c, RPC_BLOCK, c, \
				   local_buf + local_written, RPC_END))
			break;
		}
#ifdef TIOCGWINSZ
		send_window_size = 1;
#endif
		debug ("written", "my_socket", c);
		local_written += c;
		if (local_written == local_avail)
		    local_avail = local_written = 0;
	    }
	    if (FD_ISSET (my_socket, &reading)) {
		int c;
		debug ("reading", "my_socket", BUFFER_SIZE - remote_avail);
		if (!remaining_in) {
		    if (!rpc_get (my_socket, RPC_INT, &cmd, RPC_END))
			break;
		    if (cmd >= 256 && cmd != SHELL_DATA)	/* only support this command so far */
			break;
		    if (cmd < 256) {
			remote_buf[remote_avail] = (char) cmd;
			remote_buf[remote_avail + 1] = '\0';
			c = 1;
		    } else {
			if (!rpc_get (my_socket, RPC_INT, &remaining_in, RPC_END))
			    break;
			goto get_more_remaining;
		    }
		} else {
		  get_more_remaining:
		    c = MIN (remaining_in, BUFFER_SIZE - remote_avail);
		    if (!rpc_get (my_socket, RPC_BLOCK, c, remote_buf + remote_avail, RPC_END))
			break;
		    remote_buf[remote_avail + c] = '\0';
		    remaining_in -= c;
		}
		if (wait_for_prompt) {
		    if (strpbrk (remote_buf + remote_avail, prompt_chars)) {
			char cd_path[MC_MAXPATHLEN];
			int cd_len;
			wait_for_prompt = 0;
			sprintf (cd_path, "cd %s\r\n%n", path, &cd_len);
			cmd = SHELL_DATA;
			if (!rpc_send (my_socket, RPC_INT, cmd, RPC_INT, cd_len, RPC_BLOCK, cd_len, cd_path, RPC_END))
			    break;
		    }
		}
		if (!c)
		    break;
		debug ("read   ", "my_socket", c);
		remote_avail += c;
		if (c)
		    goto try_write_local;
	    }
	    if (FD_ISSET (1, &writing)) {
		int c;
	      try_write_local:
		debug ("writing", "1", remote_avail - remote_written);
		errno = 0;
		c = write (1, remote_buf + remote_written, remote_avail - remote_written);
		debug ("written", "1", c);
		debug ("written", strerror (errno), errno);
		WOULD_BLOCK (c);
		remote_written += c;
		if (remote_written == remote_avail)
		    remote_avail = remote_written = 0;
	    }
	}
    }

#ifdef SIGPIPE
    pipe_died (0);
#endif
    return 0;
}

static int the_error (int result, int errno_num)
{
    if (result == -1)
	mcfs_errno_var = errno_num;
    else
	mcfs_errno_var = 0;
    return result;
}

static char *mcfs_get_path (mcfs_connection **mc, char *path)
{
    char *user = 0, *host, *remote_path, *p;
    char *pass;
    int    port;

    /* An absolute path name, try to determine connection socket */
    if (strncmp (path, "mc:", 3) == 0){
	path += 3;

	/* 1.11.96 bor: add mc:// URL syntax */
	if (path[0] == PATH_SEP && path[1] == PATH_SEP)
	    path += 2;

	/* Port = 0 means that open_tcp_link will try to contact the
	 * remote portmapper to get the port number
	 */
	port = 0;
	if (!(remote_path = mcfs_get_host_and_username
	      (path, &host, &user, &port, &pass))){
	    free (host);
	    free (user);
	    if (pass)
	        wipe_password (pass);
	    return 0;
	}

	if (!(*mc = mcfs_open_link (host, user, &port, pass))){
	    free (remote_path);
	    free (host);
	    free (user);
	    if (pass)
	        wipe_password (pass);
	    return 0;
	}
	free (host);
	free (user);
	if (pass)
	    wipe_password (pass);
#ifdef WIN32
	char_translate (remote_path, '\\', '/');
#endif 
	return remote_path;
    }
    *mc = current_dir_connection;

    p = (char *) strdup (path);
#ifdef WIN32
    char_translate (p, '\\', '/');
#endif 
    return p;
}

/* Simple function for routines returning only an integer from the server */
static int mcfs_handle_simple_error (int sock, int return_status)
{
    int status, error;
    
    if (0 == rpc_get (sock, RPC_INT, &status, RPC_INT, &error, RPC_END))
	return the_error (-1, EIO);

    if (is_error (status, error))
	return -1;
    if (return_status)
	return status;
    return 0;
}

/* Nice wrappers */
static int mcfs_rpc_two_paths (int command, char *s1, char *s2)
{
    mcfs_connection *mc;
    char *r1, *r2;

    if ((r1 = mcfs_get_path (&mc, s1)) == 0)
	return -1;

    if ((r2 = mcfs_get_path (&mc, s2)) == 0){
	free (r1);
	return -1;
    }
    
    rpc_send (mc->sock,
	      RPC_INT, command,
	      RPC_STRING, r1,
	      RPC_STRING, r2,
	      RPC_END);
    free (r1);
    free (r2);
    return mcfs_handle_simple_error (mc->sock, 0);
}

static int mcfs_rpc_path (int command, char *path)
{
    mcfs_connection *mc;
    char *remote_file;

    if ((remote_file = mcfs_get_path (&mc, path)) == 0)
	return -1;
    
    rpc_send (mc->sock,
	      RPC_INT, command,
	      RPC_STRING, remote_file,
	      RPC_END);

    free (remote_file);
    return mcfs_handle_simple_error (mc->sock, 0);
}

static int mcfs_rpc_path_int (int command, char *path, int data)
{
    mcfs_connection *mc;
    char *remote_file;

    if ((remote_file = mcfs_get_path (&mc, path)) == 0)
	return -1;

    rpc_send (mc->sock,
	      RPC_INT,    command,
	      RPC_STRING, remote_file,
	      RPC_INT,    data, RPC_END);

    free (remote_file);
    return mcfs_handle_simple_error (mc->sock, 0);
}

static int mcfs_rpc_path_int_int (int command, char *path, int n1, int n2)
{
    mcfs_connection *mc;
    char *remote_file;

    if ((remote_file = mcfs_get_path (&mc, path)) == 0)
	return -1;

    rpc_send (mc->sock,
	      RPC_INT, command,
	      RPC_STRING, remote_file,
	      RPC_INT, n1,
	      RPC_INT, n2,
	      RPC_END);

    free (remote_file);
    return mcfs_handle_simple_error (mc->sock, 0);
}

char *mcfs_gethome (char *servername)
{
    char *remote_file;
    char *buffer;
    mcfs_connection *mc;

    if (!(remote_file = mcfs_get_path (&mc, servername)))
	return 0;
    free (remote_file);
    if (mc->home)
	return (char *) strdup (mc->home);
    else {
	rpc_send (mc->sock, RPC_INT, MC_GETHOME, RPC_END);
	if (0 == rpc_get (mc->sock, RPC_STRING, &buffer, RPC_END))
	    return (char *) strdup (PATH_SEP_STR);
	mc->home = buffer;
	return (char *) strdup (buffer);
    }
}

char *mcfs_getupdir (char *servername)
{
    char *remote_file;
    mcfs_connection *mc;
    char *buffer;

    if (!(remote_file = mcfs_get_path (&mc, servername)))
	return 0;
    free (remote_file);
    rpc_send (mc->sock, RPC_INT, MC_GETUPDIR, RPC_END);
    if (0 == rpc_get (mc->sock, RPC_STRING, &buffer, RPC_END)) {
        return (char *) strdup (PATH_SEP_STR);
    }
    return buffer;
}

static char *file_data_cache = 0;
static mcfs_handle *file_handle_cache = 0;
static int file_len_cache = 0;
static int quick_file_read = 0;

static void free_file_cache (void)
{
    if (file_data_cache) {
	free (file_data_cache);
	file_data_cache = 0;
    }
    file_handle_cache = 0;
    file_len_cache = 0;
}

/* The callbacks */
static void *mcfs_open (char *file, int flags, int mode)
{
    char *remote_file;
    mcfs_connection *mc;
    int  result, error_num;
    mcfs_handle  *remote_handle;

#ifdef WIN32
    {
	int t = flags;
	flags = 0;
	if (t & O_RDONLY)
	    flags |= 0;
	if (t & O_WRONLY)
	    flags |= 1;
	if (t & O_RDWR)
	    flags |= 02;
	if (t & O_CREAT)
	    flags |= 0100;
	if (t & O_EXCL)
	    flags |= 0200;
	if (t & O_TRUNC)
	    flags |= 01000;
	if (t & O_APPEND)
	    flags |= 02000;
    }
    if (mode >> 16)
	mode >>= 16;
#endif

    if (!(remote_file = mcfs_get_path (&mc, file)))
	return 0;

    rpc_send (mc->sock, RPC_INT, MC_OPEN, RPC_STRING, remote_file,
	      RPC_INT, flags, RPC_INT, mode, RPC_END);
    free (remote_file);

    if (0 == rpc_get (mc->sock, RPC_INT, &result, RPC_INT, &error_num, RPC_END))
	return 0;

    if (is_error (result, error_num))
	return 0;
    remote_handle = (mcfs_handle *) malloc (2 * sizeof (mcfs_handle));
    remote_handle->handle  = result;
    remote_handle->conn    = mc;

    if (!quick_file_read || flags != O_RDONLY)
	return remote_handle;
    free_file_cache ();

    file_handle_cache = remote_handle;

    if (0 == rpc_get  (mc->sock, RPC_INT, &file_len_cache, RPC_INT, &error_num, RPC_END)) {
	free_file_cache ();
	return remote_handle;
    }

    if (is_error (file_len_cache, error_num)) {
	free_file_cache ();
	return remote_handle;
    }

    file_data_cache = malloc (file_len_cache);
    mad_check (__FILE__, __LINE__);
    if (0 == rpc_get  (mc->sock, RPC_BLOCK, file_len_cache, file_data_cache, RPC_END)) {
	free_file_cache ();
	return remote_handle;
    }
    mad_check (__FILE__, __LINE__);

    return remote_handle;
}

static int mcfs_read (void *data, char *buffer, int count)
{
    mcfs_handle *info = (mcfs_handle *) data;
    int result, error;
    int handle;
    mcfs_connection *mc;

    mad_check (__FILE__, __LINE__);
    if (file_handle_cache && (unsigned long) file_handle_cache \
	== (unsigned long) info && file_data_cache) {
	int amount;
	mad_check (__FILE__, __LINE__);
	amount = count < file_len_cache ? count : file_len_cache;
	memcpy (buffer, file_data_cache, amount);
	mad_check (__FILE__, __LINE__);
	free_file_cache ();
	return amount;
    }
    mad_check (__FILE__, __LINE__);

    mc =     info->conn;
    handle = info->handle; 
    
    rpc_send (mc->sock, RPC_INT, MC_READ, RPC_INT, handle,
	      RPC_INT, count, RPC_END);
    
    if (0 == rpc_get  (mc->sock, RPC_INT, &result, RPC_INT, &error, RPC_END))
	return the_error (-1, EIO);
    
    if (is_error (result, error))
	return 0;
    
    if (0 == rpc_get  (mc->sock, RPC_BLOCK, result, buffer, RPC_END))
	return the_error (-1, EIO);
    
    return result;
}

int mcfs_write (void *data, char *buf, int nbyte)
{
    mcfs_handle *info = (mcfs_handle *) data;
    mcfs_connection *mc;
    int handle;

    if (file_handle_cache == info)
	free_file_cache ();

    mc     = info->conn;
    handle = info->handle;
    
    if (nbyte > 8192)
	nbyte = 8192;

    rpc_send (mc->sock,
	      RPC_INT, MC_WRITE,
	      RPC_INT, handle,
	      RPC_INT, nbyte,
	      RPC_BLOCK, nbyte, buf,
	      RPC_END);
    
    return mcfs_handle_simple_error (mc->sock, 1);
}

static int mcfs_close (void *data)
{
    mcfs_handle *info = (mcfs_handle *) data;
    mcfs_connection *mc;
    int handle, result, error;

    if (!data)
	return -1;

    if (quick_file_read) {
	free (data);
	return 0;
    }
    
    if (file_handle_cache == info)
	free_file_cache ();

    handle = info->handle;
    mc     = info->conn;
    
    rpc_send (mc->sock, RPC_INT, MC_CLOSE, RPC_INT, handle, RPC_END);
    
    if (0 == rpc_get (mc->sock, RPC_INT, &result, RPC_INT, &error, RPC_END))
	return the_error (-1, EIO);
    
    is_error (result, error);

    free (data);
    return result;
}

static int mcfs_errno (void)
{
    return mcfs_errno_var;
}

typedef struct dir_entry {
    char *text;
    struct dir_entry *next;
    struct stat my_stat;
    int    merrno;
} dir_entry;

typedef struct {
    mcfs_connection *conn;
    int handle;
    dir_entry *entries;
    dir_entry *current;
} opendir_info;

static void *mcfs_opendir (char *dirname)
{
    opendir_info *mcfs_info;
    mcfs_connection *mc;
    int handle, error_num;
    char *remote_dir;
    int  result;

    if (!(remote_dir = mcfs_get_path (&mc, dirname)))
	return 0;
    rpc_send (mc->sock, RPC_INT, MC_OPENDIR, RPC_STRING, remote_dir, RPC_END);
    free (remote_dir);
    
    if (0 == rpc_get  (mc->sock, RPC_INT, &result, RPC_INT, &error_num, RPC_END))
	return 0;
    
    if (is_error (result, error_num))
	return 0;
	    
    handle = result;

    mcfs_info = (opendir_info *) malloc (sizeof(opendir_info));
    mcfs_info->conn = mc;
    mcfs_info->handle = handle;
    mcfs_info->entries = 0;
    mcfs_info->current = 0;
    
    return mcfs_info;
}

static int get_stat_info (mcfs_connection *mc, struct stat *buf);

static int mcfs_loaddir (opendir_info *mcfs_info)
{
    int  status, error;
    mcfs_connection  *mc = mcfs_info->conn;
    int  liink = mc->sock;
    int  first = 1;

    rpc_send (liink, RPC_INT, MC_READDIR, RPC_INT, mcfs_info->handle, RPC_END);

    for (;;){
	int  entry_len;
	dir_entry *new_entry;

	if (!rpc_get (liink, RPC_INT, &entry_len, RPC_END))
	    return 0;
	
	if (entry_len == 0)
	    break;

	new_entry = malloc (sizeof (dir_entry));
	new_entry->text = malloc (entry_len + 1);
	new_entry->text [entry_len] = 0;

	new_entry->next = 0;
	if (first){
	    mcfs_info->entries = new_entry;
	    mcfs_info->current = new_entry;
	    first = 0;
	} else {
	    mcfs_info->current->next = new_entry;
	    mcfs_info->current = new_entry;
	}

	if (!rpc_get (liink, RPC_BLOCK, entry_len, new_entry->text, RPC_END))
	    return 0;

	/* Then we get the status from the lstat */
	if (!rpc_get (liink, RPC_INT, &status, RPC_INT, &error, RPC_END))
	    return 0;
	
	if (is_error (status, error))
	    new_entry->merrno = error;
	else {
	    new_entry->merrno = 0;
	    if (!get_stat_info (mc, &(new_entry->my_stat)))
		return 0;
	}
    } 
    mcfs_info->current = mcfs_info->entries;
    
    return 1;
}

void mcfs_free_dir (dir_entry *de)
{
    if (!de)
	return;
    mcfs_free_dir (de->next);
    free (de->text);
    free (de);
}

/* Explanation:
 * On some operating systems (Slowaris 2 for example)
 * the d_name member is just a char long (Nice trick that break everything,
 * so we need to set up some space for the filename.
 */
static struct {
    struct dirent dent;
    char extra_buffer [MC_MAXPATHLEN - sizeof (struct dirent)];
} mcfs_readdir_data;

/* The readdir routine loads the complete directory */
/* It's too slow to ask the server each time */
/* It now also sends the complete lstat information for each file */
static struct stat *cached_lstat_info;
static void *mcfs_readdir (void *info)
{
    opendir_info  *mcfs_info;

    mcfs_info = (opendir_info *) info;

    if (!mcfs_info->entries)
	if (!mcfs_loaddir (mcfs_info))
	    return NULL;

    if (mcfs_info->current == 0){
	cached_lstat_info = 0;
	mcfs_free_dir (mcfs_info->entries);
	mcfs_info->entries = 0;
	return NULL;
    }
#ifdef WIN32
    mcfs_readdir_data.dent.d_name = mcfs_readdir_data.extra_buffer;
    strcpy (mcfs_readdir_data.extra_buffer, mcfs_info->current->text);
#else
    strcpy (&(mcfs_readdir_data.dent.d_name [0]), mcfs_info->current->text);
#endif
    cached_lstat_info = &mcfs_info->current->my_stat;
    mcfs_info->current = mcfs_info->current->next;
#ifdef HAVE_DIR_D_NAMELEN
    mcfs_readdir_data.dent.d_namlen = strlen (mcfs_readdir_data.dent.d_name);
#endif
    return &mcfs_readdir_data;
}

static int mcfs_closedir (void *info)
{
    opendir_info *mcfs_info = (opendir_info *) info;
    dir_entry *p, *q;

    rpc_send (mcfs_info->conn->sock, RPC_INT, MC_CLOSEDIR,
	      RPC_INT, mcfs_info->handle, RPC_END);
    
    for (p = mcfs_info->entries; p;){
	q = p;
	p = p->next;
	free (q->text);
	free (q);
    }
    free (info);
    return 0;
}

static time_t mcfs_get_time (mcfs_connection *mc)
{
    int       sock = mc->sock;
    
    if (mc->version == 1) {
	struct tm tt;

	rpc_get (sock,
		 RPC_INT, &tt.tm_sec,
		 RPC_INT, &tt.tm_min,
		 RPC_INT, &tt.tm_hour,
		 RPC_INT, &tt.tm_mday,
		 RPC_INT, &tt.tm_year,
		 RPC_INT, &tt.tm_mon,
		 RPC_END);
	tt.tm_year = tt.tm_year - 1900;
	tt.tm_isdst = 0;

	return mktime (&tt);
    } else {
	char  *buf;
	long  tm;

	rpc_get (sock,
		 RPC_STRING, &buf,
		 RPC_END);
	sscanf (buf, "%lx", &tm);
	free (buf);

	return (time_t) tm;
    }
}

dev_t long_to_dev (long mylong);

static int get_stat_info (mcfs_connection * mc, struct stat *buf)
{
    long mylong;
    int sock = mc->sock;

    buf->st_dev = long_to_dev (0);

    rpc_get (sock, RPC_INT, &mylong, RPC_END);
#ifdef HAVE_ST_RDEV
    buf->st_rdev = long_to_dev (mylong);
#endif
    rpc_get (sock, RPC_INT, &mylong, RPC_END);
    buf->st_ino = mylong;
    rpc_get (sock, RPC_INT, &mylong, RPC_END);
#ifdef WIN32
    buf->st_mode = (unsigned long) mylong | ((unsigned long) (mylong & (S_IRWXU | S_IFREG | S_IFDIR)) << 16);
#else
    buf->st_mode = mylong;
#endif
    rpc_get (sock, RPC_INT, &mylong, RPC_END);
    buf->st_nlink = mylong;
    rpc_get (sock, RPC_INT, &mylong, RPC_END);
    buf->st_uid = mylong;
    rpc_get (sock, RPC_INT, &mylong, RPC_END);
    buf->st_gid = mylong;
    rpc_get (sock, RPC_INT, &mylong, RPC_END);
    buf->st_size = mylong;

    if (!rpc_get (sock, RPC_INT, &mylong, RPC_END))
	return 0;
#ifdef HAVE_ST_BLOCKS
    buf->st_blocks = mylong;
#endif
    buf->st_atime = mcfs_get_time (mc);
    buf->st_mtime = mcfs_get_time (mc);
    buf->st_ctime = mcfs_get_time (mc);
    return 1;
}

void mcfs_init (void)
{
    return;
}

static int mcfs_stat_cmd (int cmd, char *path, struct stat *buf)
{
    char *remote_file;
    mcfs_connection *mc;
    int  status, error;
    
    if ((remote_file = mcfs_get_path (&mc, path)) == 0)
	return -1;

    rpc_send (mc->sock, RPC_INT, cmd, RPC_STRING, remote_file, RPC_END);
    free (remote_file);
    if (!rpc_get (mc->sock, RPC_INT, &status, RPC_INT, &error, RPC_END))
	return the_error (-1, errno);

    if (is_error (status, error))
	return -1;

    if (get_stat_info (mc, buf))
	return 0;
    else
	return the_error (-1, EIO);
}

static int mcfs_stat (char *path, struct stat *buf)
{
    return mcfs_stat_cmd (MC_STAT, path, buf);
}

static int mcfs_lstat (char *path, struct stat *buf)
{
    int path_len = strlen (path);
    int entry_len = strlen (mcfs_readdir_data.dent.d_name);

    /* Hack ... */
    if (strcmp (path + path_len - entry_len,
		mcfs_readdir_data.dent.d_name) == 0 &&
	cached_lstat_info){
	*buf = *cached_lstat_info;
	return 0;
    }
    return mcfs_stat_cmd (MC_LSTAT, path, buf);
}

int mcfs_fstat (void *data, struct stat *buf)
{
    mcfs_handle *info = (mcfs_handle *) data;
    int result, error;
    int handle, sock;
    
    sock = info->conn->sock;
    handle = info->handle;
    
    rpc_send (sock, RPC_INT, MC_FSTAT, RPC_INT, handle, RPC_END);
    if (!rpc_get (sock, RPC_INT, &result, RPC_INT, &error, RPC_END))
	return the_error (-1, EIO);

    if (is_error (result, error))
	return -1;

    if (get_stat_info (info->conn, buf))
	return 0;
    else
	return the_error (-1, EIO);
}

int mcfs_chmod (char *path, unsigned long mode)
{
#ifdef WIN32
    if ((mode >> 16))
	mode >>= 16;
#endif
    return mcfs_rpc_path_int (MC_CHMOD, path, mode);
}

int mcfs_chown (char *path, int owner, int group)
{
    return mcfs_rpc_path_int_int (MC_CHOWN, path, owner, group);
}

int mcfs_utime (char *path, struct utimbuf *times)
{
    mcfs_connection   *mc;
    int status;
    char *file;

    if (!(file = mcfs_get_path (&mc, path)))
	return -1;

    status = 0;
    if (mc->version >= 2) {
	char   abuf[2*sizeof(long) + 1];
	char   mbuf[2*sizeof(long) + 1];
	long   atime, mtime;

	atime = (long) times->actime;
	mtime = (long) times->modtime;

	sprintf (abuf, "%lx", atime);
	sprintf (mbuf, "%lx", mtime);

	rpc_send (mc->sock, RPC_INT, MC_UTIME,
			RPC_STRING, file,
			RPC_STRING, abuf,
			RPC_STRING, mbuf,
			RPC_END);
	status = mcfs_handle_simple_error (mc->sock, 0);
    
    }
    free (file);
    return (status);
}

static int mcfs_readlink (char *path, char *buf, int size)
{
    char *remote_file, *stat_str;
    int  status, error;
    mcfs_connection *mc;

    if (!(remote_file = mcfs_get_path (&mc, path)))
	return -1;

    rpc_send (mc->sock, RPC_INT, MC_READLINK, RPC_STRING, remote_file, RPC_END);
    free (remote_file);
    if (!rpc_get (mc->sock, RPC_INT, &status, RPC_INT, &error, RPC_END))
	return the_error (-1, EIO);

    if (is_error (status, errno))
	return -1;

    if (!rpc_get (mc->sock, RPC_STRING, &stat_str, RPC_END))
	return the_error (-1, EIO);
    
    strncpy (buf, stat_str, size);
    free (stat_str);
    return strlen (buf);
}

int mcfs_unlink (char *path)
{
    free_file_cache ();
    return mcfs_rpc_path (MC_UNLINK, path);
}

int mcfs_symlink (char *n1, char *n2)
{
    return mcfs_rpc_two_paths (MC_SYMLINK, n1, n2);
}

int mcfs_rename (char *a, char *b)
{
    free_file_cache ();
    return mcfs_rpc_two_paths (MC_RENAME, a, b);
}

static int mcfs_chdir (char *path)
{
    char *remote_dir;
    mcfs_connection *mc;
    int  status, error;

    if (!(remote_dir = mcfs_get_path (&mc, path)))
	return -1;

    if (mcfs_current_dir)
	free (mcfs_current_dir);
    
    mcfs_current_dir = (char *) strdup (path);
    
    current_dir_connection = mc;
    rpc_send (mc->sock, RPC_INT, MC_CHDIR, RPC_STRING, remote_dir, RPC_END);
    free (remote_dir);
    if (!rpc_get (mc->sock, RPC_INT, &status, RPC_INT, &error, RPC_END))
	return the_error (-1, EIO);
    
    if (is_error (status, error))
	return -1;
    return 0;
}

int mcfs_lseek (void *data, off_t offset, int whence)
{
    mcfs_handle *info = (mcfs_handle *) data;
    int handle, sock;

    if (file_handle_cache == info)
	free_file_cache ();

    sock = info->conn->sock; 
    handle = info->handle;

    rpc_send (sock,
	      RPC_INT, MC_LSEEK,
	      RPC_INT, handle,
	      RPC_INT, offset,
	      RPC_INT, whence,
	      RPC_END);
    return mcfs_handle_simple_error (sock, 1);
}

long dev_to_long (dev_t dev);

int mcfs_mknod (char *path, int mode, dev_t dev)
{
    return mcfs_rpc_path_int_int (MC_MKNOD, path, mode, dev_to_long (dev));
}

int mcfs_mkdir (char *path, unsigned long mode)
{
    return mcfs_rpc_path_int (MC_MKDIR, path, mode);
}

int mcfs_rmdir (char *path)
{
    return mcfs_rpc_path (MC_RMDIR, path);
}

int mcfs_link (char *p1, char *p2)
{
    return mcfs_rpc_two_paths (MC_LINK, p1, p2);
}

/* We do not free anything right now: we free resources when we run
 * out of them
 */
static vfsid mcfs_getid (char *p, struct vfs_stamping **parent)
{
    *parent = NULL;
    
    return (vfsid) -1;
}

static int mcfs_nothingisopen (vfsid id)
{
    return 0;
}

static void mcfs_free (vfsid id)
{
    /* FIXME: Should not be empty */
}

static char *mcfs_getlocalcopy (char *path)
{
    return mc_def_getlocalcopy (path);
}

static void mcfs_ungetlocalcopy (char *path, char *local, int has_changed)
{
    mc_def_ungetlocalcopy (path, local, has_changed);
}

/* Gives up on a socket and reopnes the connection, the child own the socket
 * now
 */
void
mcfs_forget (char *path)
{
    char *host, *user, *pass, *p;
    int  port, i, vers;
    
    if (strncmp (path, "mc:", 3) != 0)
	return;
    path += 3;
    if (path[0] == PATH_SEP && path[1] == PATH_SEP)
	path += 2;

    if ((p = mcfs_get_host_and_username (path, &host, &user, &port, &pass)) == 0) {
	free (host);
	free (user);
	if (pass)
	    wipe_password (pass);
	return;
    }
    for (i = 0; i < MCFS_MAX_CONNECTIONS; i++){
	if ((strcmp (host, mcfs_connections [i].host) == 0) &&
	    (strcmp (user, mcfs_connections [i].user) == 0) &&
	    (port == mcfs_connections [i].port)){

	    /* close socket: the child owns it now */
	    shutdown (mcfs_connections [i].sock, 2);

	    /* reopen the connection */
	    mcfs_connections [i].sock = mcfs_open_tcp_link (host, user, &port, pass, &vers);
	}
    }
    free (p);
    free (host);
    free (user);
    if (pass)
	wipe_password (pass);
}

static int 
mcfs_setctl (char *path, int ctlop, char *arg)
{
    char *remote_file;
    mcfs_connection *mc;
    if (ctlop != MCCTL_QUICKFILEREAD)
	return 1;
    if (!(remote_file = mcfs_get_path (&mc, path)))
	return 1;
    free (remote_file);
    switch (ctlop) {
    case MCCTL_QUICKFILEREAD:
	if (mc->version == 3) {
	    rpc_send (mc->sock, RPC_INT, MC_QUICKFILEREAD, RPC_END);
	    quick_file_read = 1;
	}
	return 0;
    }
    return 0;
}

#ifdef HAVE_MMAP
caddr_t mcfs_mmap (caddr_t addr, size_t len, int prot, int flags, void *data, off_t offset)
{
    return (caddr_t)-1; /* We do not mmap to far away */
}

int mcfs_munmap (caddr_t addr, size_t len, void *data)
{
    return -1;
}
#endif

vfs mcfs_vfs_ops = {
    mcfs_open,
    mcfs_close,
    mcfs_read,
    mcfs_write,
    
    mcfs_opendir,
    mcfs_readdir,
    mcfs_closedir,

    mcfs_stat,
    mcfs_lstat,
    mcfs_fstat,

    mcfs_chmod,
    mcfs_chown,
    mcfs_utime,

    mcfs_readlink,
    mcfs_symlink,
    mcfs_link,
    mcfs_unlink,

    mcfs_rename,
    mcfs_chdir,
    mcfs_errno,
    mcfs_lseek,
    mcfs_mknod,
    
    mcfs_getid,
    mcfs_nothingisopen,
    mcfs_free,
    
    mcfs_getlocalcopy,
    mcfs_ungetlocalcopy,

    mcfs_mkdir,
    mcfs_rmdir,
    NULL,
    mcfs_setctl,
    mcfs_forget
#ifdef HAVE_MMAP
    , mcfs_mmap,
    mcfs_munmap
#endif
};

