/* copyright (c) 2005
 * the regents of the university of michigan
 * all rights reserved
 * 
 * permission is granted to use, copy, create derivative works and
 * redistribute this software and such derivative works for any purpose,
 * so long as the name of the university of michigan is not used in
 * any advertising or publicity pertaining to the use or distribution
 * of this software without specific, written prior authorization.  if
 * the above copyright notice or any other identification of the
 * university of michigan is included in any copy of any portion of
 * this software, then the disclaimer below must also be included.
 * 
 * this software is provided as is, without representation from the
 * university of michigan as to its fitness for any purpose, and without
 * warranty by the university of michigan of any kind, either express 
 * or implied, including without limitation the implied warranties of
 * merchantability and fitness for a particular purpose.  the regents
 * of the university of michigan shall not be liable for any damages,   
 * including special, indirect, incidental, or consequential damages, 
 * with respect to any claim arising out or in connection with the use
 * of the software, even if it has been or is hereafter advised of the
 * possibility of such damages.
 */

#include <osi.h>
#include "afsd.h"
#include <winioctl.h>
#include "..\afsrdr\kif.h"
#include "..\afsrdr\ifs_rpc.h"

#include "afsdifs.h"


/****************************/
/* parameters, macros, etc. */
/****************************/
#define IFSL_SUCCEEDED(st)		(!(st & IFSL_FAIL_BASE))
#define MAP_RETURN(code)		if (code) return ifs_MapCmError(code);
#define ROOTPATH			"\\"
#define TRANSFER_BUF_SIZE		(RPC_BUF_SIZE + TRANSFER_CHUNK_SIZE)
#define SCPL_LOCK			EnterCriticalSection(&scp_list_lock);
#define SCPL_UNLOCK			LeaveCriticalSection(&scp_list_lock);


/****************************/
/* structs                  */
/****************************/
struct user_map_entry			/* how we keep users straight.  total of MAX_AFS_USERS of these */
{
    LARGE_INTEGER id;			/* internal id created by kernel */
    cm_user_t *creds;			/* global (thread-specific) var userp is set to this */
};

struct scp_status				/* one for each unique file in afs */
{
    struct scp_status *next;	/* stored in a global chain in a chain locked by SCPL_[UN]LOCK */
    cm_scache_t *scp;			/* file handle used with cm_ fns */
    ULONG fid;					/* internal id generated by FID_HASH_FN from AFS's 128-bit FID */
};
typedef struct scp_status scp_status_t;

struct readdir_context			/* temporary struct, allocated as necessary, for cm_Apply callback */
{
    char *matchString;			/* for matching against */
    char *buf, *buf_pos;		/* filling buffer to length, currently at buf_pos */
    ULONG_PTR length;
    ULONG count;			/* number of entries packed so far */
};
typedef struct readdir_context readdir_context_t;


/****************************/
/* global vars              */
/****************************/
/* the table user_map is used to store cm_user structs keyed on the calling
 * process' 64-bit ID token.  the rpc library sets userp to point to this
 * entry; userp is specific to each thread, and thus may be used securely
 * as a global.
 */
__declspec(thread) cm_user_t *userp;
struct user_map_entry user_map[MAX_AFS_USERS];

CRITICAL_SECTION mapLock, scp_list_lock;

scp_status_t *scp_list_head = NULL;


/****************************/
/* error functions          */
/****************************/
char *IfslErrorToText(unsigned long ifsl)
{
    switch (ifsl)
    {
    case IFSL_SUCCESS:
        return "success";
    case IFSL_DOES_NOT_EXIST:
        return "does not exist";
    case IFSL_NOT_IMPLEMENTED:
        return "not implemented";
    case IFSL_END_OF_ENUM:
        return "end of enum";
    case IFSL_CANNOT_MAKE:
        return "cannot make";
    case IFSL_END_OF_FILE:
        return "end of file";
    case IFSL_NO_ACCESS:
        return "no access";
    case IFSL_BUFFER_TOO_SMALL:
        return "buffer too small";
    case IFSL_SHARING_VIOLATION:
        return "sharing violation";
    case IFSL_BAD_INPUT:
        return "bad input";
    case IFSL_GENERIC_FAILURE:
        return "generic failure";
    case IFSL_OPEN_CREATED:
        return "open created";
    case IFSL_OPEN_EXISTS:
        return "open exists";
    case IFSL_OPEN_OPENED:
        return "opened";
    case IFSL_OPEN_OVERWRITTEN:
        return "overwritten";
    case IFSL_OPEN_SUPERSCEDED:
        return "supersceded";
    case IFSL_BADFILENAME:
        return "bad filename";
    case IFSL_READONLY:
        return "read only";
    case IFSL_IS_A_DIR:
        return "is a dir";
    case IFSL_PATH_DOES_NOT_EXIST:
        return "path does not exist";
    case IFSL_IS_A_FILE:
        return "is a file";
    case IFSL_NOT_EMPTY:
        return "dir not empty";
    case IFSL_UNSPEC:
        return "unspecified error";
    default:
        return "NOT FOUND";
    }
}       

unsigned long ifs_MapCmError(unsigned long code)
{
    switch (code)
    {
    case CM_ERROR_STOPNOW:
    case 0:
        return IFSL_SUCCESS;
    case CM_ERROR_NOSUCHCELL:
    case CM_ERROR_NOSUCHVOLUME:
    case CM_ERROR_NOSUCHFILE:
        return IFSL_DOES_NOT_EXIST;
    case CM_ERROR_NOSUCHPATH:
        return IFSL_PATH_DOES_NOT_EXIST;
    case CM_ERROR_BADNTFILENAME:
        return IFSL_BADFILENAME;
    case CM_ERROR_TIMEDOUT:
    case CM_ERROR_ALLOFFLINE:
    case CM_ERROR_CLOCKSKEW:
    case CM_ERROR_REMOTECONN:
    case CM_ERROR_ALLBUSY:
        return IFSL_GENERIC_FAILURE;
    case CM_ERROR_NOACCESS:
        return IFSL_NO_ACCESS;
    case CM_ERROR_RETRY:
    case CM_ERROR_TOOBIG:
    case CM_ERROR_BADFD:
    case CM_ERROR_BADFDOP:
    case CM_ERROR_CROSSDEVLINK:
        return IFSL_GENERIC_FAILURE;
    case CM_ERROR_EXISTS:
        return IFSL_OPEN_EXISTS;
    case CM_ERROR_BADOP:
    case CM_ERROR_INVAL:
	case CM_ERROR_UNKNOWN:
    case CM_ERROR_BADSMB:
        return IFSL_GENERIC_FAILURE;
    case CM_ERROR_NOTDIR:
    case CM_ERROR_ISDIR:
    case CM_ERROR_READONLY:
        return IFSL_BAD_INPUT;
    case CM_ERROR_BUFFERTOOSMALL:
        return IFSL_BUFFER_TOO_SMALL;
    case CM_ERROR_WOULDBLOCK:
    case CM_ERROR_BADSHARENAME:
    case CM_ERROR_NOMORETOKENS:
    case CM_ERROR_NOTEMPTY:
    case CM_ERROR_USESTD:
    case CM_ERROR_ATSYS:
        return IFSL_GENERIC_FAILURE;
    case CM_ERROR_NOFILES:
    case CM_ERROR_BADTID:
        return IFSL_END_OF_ENUM;
    case CM_ERROR_PARTIALWRITE:
    case CM_ERROR_NOIPC:
    case CM_ERROR_RENAME_IDENTICAL:
    case CM_ERROR_AMBIGUOUS_FILENAME:
        return IFSL_GENERIC_FAILURE;
    case IFSL_SHARING_VIOLATION:
        return IFSL_SHARING_VIOLATION;
    case IFSL_NOT_EMPTY:
        return IFSL_NOT_EMPTY;
    case CM_ERROR_SPACE:
    case CM_ERROR_QUOTA:
        return IFSL_OVERQUOTA;	
    }
    return IFSL_GENERIC_FAILURE;
}


/****************************/
/* support fns              */
/****************************/
cm_scache_t *ifs_FindScp(ULONG fid)		/* walk list to find scp<->fid mapping */
{
    scp_status_t *curr;

    SCPL_LOCK;

    curr = scp_list_head;
    while (curr)
    {
	if (curr->fid == fid)
        {
            SCPL_UNLOCK;
            return curr->scp;
        }
	curr = curr->next;
    }
    SCPL_UNLOCK;
    return NULL;
}

/* must call with scp write-locked.  will always return correct results
   unless network fails (it loops properly). */
ifs_CheckAcl(cm_scache_t *scp, ULONG access, ULONG *granted)
{
    long code;
    cm_req_t req;

    cm_InitReq(&req);

    /* ripped from cm_scache.c */
    while (1)
    {
	if (cm_HaveAccessRights(scp, userp, access, granted))
        {
            return 0;
        }
	else
        {
            /* we don't know the required access rights */
            code = cm_GetAccessRights(scp, userp, &req);
            MAP_RETURN(code);
            continue;
        }
    }

    return 0;
}

/* extract data from scp.  an ifs_ support function to centralize changes. */
ifs_CopyInfo(cm_scache_t *scp, ULONG *attribs, LARGE_INTEGER *size,
              LARGE_INTEGER *creation, LARGE_INTEGER *access,
              LARGE_INTEGER *change, LARGE_INTEGER *written)
{
    access->QuadPart = 0;						/* these mappings are not quite correct.  we can */
    change->QuadPart = scp->clientModTime;		/* leave them zero, if necessary. */
    written->QuadPart = scp->clientModTime;
    creation->QuadPart = scp->serverModTime;

    *attribs = 0;
    if (scp->fileType == CM_SCACHETYPE_DIRECTORY ||
        scp->fileType == CM_SCACHETYPE_SYMLINK ||
	scp->fileType == CM_SCACHETYPE_MOUNTPOINT/* ||
	scp->fileType == 0*/)
	*attribs |= FILE_ATTRIBUTE_DIRECTORY;

    /*if (!attribs && scp->fileType == CM_SCACHETYPE_FILE)
    *attribs |= FILE_ATTRIBUTE_NORMAL;*/

    if (*attribs == FILE_ATTRIBUTE_DIRECTORY)
	size->QuadPart = 0;
    else
	*size = scp->length;

    return 0;
}


/* close and zero scp pointer.  zeroing pointer should
   help eliminate accessing discarded cache entries. */
void ifs_InternalClose(cm_scache_t **scpp)
{
    osi_assert(scpp && *scpp);

    lock_ObtainMutex(&((*scpp)->mx));
    cm_ReleaseSCache(*scpp);

    lock_ReleaseMutex(&((*scpp)->mx));
    *scpp = NULL;
}

/* normalizes path by removing trailing slashes.  separates last
 * path component with a null, so that *dirp points to parent path
 * and *filep points to filename.  modifies string path.
 */
BOOLEAN ifs_FindComponents(char *path, const char **dirp, const char **filep)
{
    char *lastSep;
    BOOLEAN removed;
    static char emptyPath[] = "\\";		/* if the path contains only one component, this is the parent. */

    osi_assert(path);

    if (strlen(path))
	removed = (path[strlen(path)-1] == '\\');
    else
	removed = 1;

    lastSep = strrchr(path, '\\');
    while (lastSep == path + strlen(path) - 1)
    {
	*lastSep = '\0';
	lastSep = strrchr(path, '\\');
    }

    if (lastSep)
    {
	*lastSep = '\0';

	*dirp = path;
	*filep = lastSep + 1;
    }
    else    
    {
	lastSep = path + strlen(path);

	*dirp = emptyPath;
	*filep = path;
    }

    return removed;
}

/* here to make maintenance easy */
unsigned long ifs_ConvertFileName(wchar_t *in, unsigned int inchars, char *out, unsigned int outchars)
{
    unsigned long code;

    code = WideCharToMultiByte(CP_UTF8, 0/*WC_NO_BEST_FIT_CHARS*/, in, inchars, out, outchars-1, NULL, NULL);
    if (!code)
	return IFSL_BADFILENAME;

    return 0;
}

/* called by rpc_ library to let us initialize environment.
 * call with id of zero to clear current thread auth. */
ifs_ImpersonateClient(LARGE_INTEGER user_id)
{
    int x, empty;

    if (!user_id.QuadPart)
    {
	userp = NULL;
	return 0;
    }

    empty = -1;
    EnterCriticalSection(&mapLock);
    for (x = 0; x < MAX_AFS_USERS; x++)
    {
	if (user_map[x].id.QuadPart == 0)
            empty = x;
 	if (user_map[x].id.QuadPart == user_id.QuadPart)
            goto done;
    }
    if (empty == -1)
    {
	LeaveCriticalSection(&mapLock);
	return -1;
    }
    user_map[empty].id = user_id;
    user_map[empty].creds = cm_NewUser();
    x = empty;

  done:
    userp = user_map[x].creds;
    LeaveCriticalSection(&mapLock);

    return 0;
}


/****************************/
/* upcalls                  */
/****************************/
long uc_namei(WCHAR *name, ULONG *fid)	/* performs name<->fid mapping, and enters it into table */
{
    char *buffer;				/* we support semi-infinite path lengths */
    long code;
    cm_scache_t *scp, *dscp;
    char *dirp, *filep;
    cm_req_t req;
    scp_status_t *st;
    short len;

    cm_InitReq(&req);

    len = (short)wcslen(name)+20;			/* characters *should* map 1<->1, but in case */
    buffer = malloc(len);
    code = ifs_ConvertFileName(name, -1, buffer, len);
    if (code)
    {
	free(buffer);
	MAP_RETURN(code);
    }
    ifs_FindComponents(buffer, &dirp, &filep);

    code = cm_NameI(cm_data.rootSCachep, dirp, CM_FLAG_CASEFOLD | CM_FLAG_FOLLOW | CM_FLAG_CHECKPATH, userp, ROOTPATH, &req, &dscp);
    if (code)
    {
	free(buffer);
	MAP_RETURN(code);
    }
    if (*filep)
	code = cm_Lookup(dscp, filep, 0, userp, &req, &scp);
    else
	cm_HoldSCache(scp = dscp);
    cm_ReleaseSCache(dscp);

    if (code)
    {
	free(buffer);
	MAP_RETURN(code);
    }

    if (ifs_FindScp(FID_HASH_FN(&scp->fid)))
	{
	osi_assertx(ifs_FindScp(FID_HASH_FN(&scp->fid)) == scp, "uc_namei: same fid hash for two files");
	*fid = FID_HASH_FN(&scp->fid);
	osi_assert(scp->refCount > 1);
	cm_ReleaseSCache(scp);
	}
    else
	{
	SCPL_LOCK;
	st = malloc(sizeof(scp_status_t));
	st->scp = scp;
	st->fid = FID_HASH_FN(&scp->fid);
	st->next = scp_list_head;
	scp_list_head = st;
	SCPL_UNLOCK;
	osi_assert(scp->refCount == 1);
	*fid = st->fid;
	}

    free(buffer);

    return 0;
}

/* this should only be called right after open, so we do not need to stat file.
 * we only check the server's restrictions.  sharing violations are handled in the
 * kernel. the access mode we grant sticks with the file_object until its death. */
long uc_check_access(ULONG fid, ULONG access, ULONG *granted)
{
    ULONG afs_acc, afs_gr;
    cm_scache_t *scp;
    ULONG gr;
    BOOLEAN file, dir;

    gr = 0;
 
    scp = ifs_FindScp(fid);
    if (!scp)
	return IFSL_BAD_INPUT;

    file = (scp->fileType == CM_SCACHETYPE_FILE);
    dir = !file;

    /* access definitions from prs_fs.h */
    afs_acc = 0;
    if (access & FILE_READ_DATA)
	afs_acc |= PRSFS_READ;
    if (file && ((access & FILE_WRITE_DATA) || (access & FILE_APPEND_DATA)))
	afs_acc |= PRSFS_WRITE;
    if (access & FILE_WRITE_EA || access & FILE_WRITE_ATTRIBUTES)
	afs_acc |= PRSFS_WRITE;
    if (dir && ((access & FILE_ADD_FILE) || (access & FILE_ADD_SUBDIRECTORY)))
	afs_acc |= PRSFS_INSERT;
    if (dir && (access & FILE_LIST_DIRECTORY))
	afs_acc |= PRSFS_LOOKUP;
    if (access & FILE_READ_EA || access & FILE_READ_ATTRIBUTES)
	afs_acc |= PRSFS_LOOKUP;
    if (file && (access & FILE_EXECUTE))
	afs_acc |= PRSFS_WRITE;
    if (dir && (access & FILE_TRAVERSE))
	afs_acc |= PRSFS_READ;
    if (dir && (access & FILE_DELETE_CHILD))
	afs_acc |= PRSFS_DELETE;
    if ((access & DELETE))
	afs_acc |= PRSFS_DELETE;

    /* check ACL with server */
    lock_ObtainMutex(&(scp->mx));
    ifs_CheckAcl(scp, afs_acc, &afs_gr);
    lock_ReleaseMutex(&(scp->mx));

    *granted = 0;
    if (afs_gr & PRSFS_READ)
	*granted |= FILE_READ_DATA | FILE_EXECUTE;
    if (afs_gr & PRSFS_WRITE)
	*granted |= FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES | FILE_EXECUTE;
    if (afs_gr & PRSFS_INSERT)
	*granted |= (dir ? FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY : 0) | (file ? FILE_ADD_SUBDIRECTORY : 0);
    if (afs_gr & PRSFS_LOOKUP)
 	*granted |= (dir ? FILE_LIST_DIRECTORY : 0) | FILE_READ_EA | FILE_READ_ATTRIBUTES;
    if (afs_gr & PRSFS_DELETE)
	*granted |= FILE_DELETE_CHILD | DELETE;
    if (afs_gr & PRSFS_LOCK)
 	*granted |= 0;
    if (afs_gr & PRSFS_ADMINISTER)
	*granted |= 0;

    * granted |= SYNCHRONIZE | READ_CONTROL;

    return 0;
}

long uc_create(WCHAR *name, ULONG attribs, LARGE_INTEGER alloc, ULONG access, ULONG *granted, ULONG *fid)
{
    char *buffer;					/* we support semi-infinite path lengths */
    long code;
    cm_scache_t *scp, *dscp;
    char *dirp, *filep;
    unsigned char removed;
    cm_req_t req;
    scp_status_t *st;
    cm_attr_t attr;
    short len;

    cm_InitReq(&req);

    len = (short)wcslen(name)+20;			/* characters *should* map 1<->1, but in case */
    buffer = malloc(len);
    code = ifs_ConvertFileName(name, -1, buffer, len);
    if (code)
    {
	free(buffer);
	MAP_RETURN(code);
    }
    removed = ifs_FindComponents(buffer, &dirp, &filep);

    /* lookup the parent directory, which must exist */
    code = cm_NameI(cm_data.rootSCachep, dirp, CM_FLAG_CASEFOLD | CM_FLAG_FOLLOW | CM_FLAG_CHECKPATH, userp, ROOTPATH, &req, &dscp);
    if (code)
    {
	free(buffer);
	MAP_RETURN(code);
    }

    osi_assert(filep);
    if (*filep)
    {
	attr.mask = CM_ATTRMASK_LENGTH;
	attr.length = alloc;

	if (attribs & FILE_ATTRIBUTE_DIRECTORY)
        {
            code = cm_MakeDir(dscp, filep, 0, &attr, userp, &req);
            if (!code)
                code = cm_Lookup(dscp, filep, 0, userp, &req, &scp);
        }
	else
	    code = cm_Create(dscp, filep, 0, &attr, &scp, userp, &req);
    }
    cm_ReleaseSCache(dscp);

    if (code)
    {
	free(buffer);
	MAP_RETURN(code);
    }

    SCPL_LOCK;
    st = malloc(sizeof(scp_status_t));
    st->scp = scp;
    st->fid = FID_HASH_FN(&scp->fid);
    st->next = scp_list_head;
    scp_list_head = st;
    SCPL_UNLOCK;

    *fid = st->fid;
    *granted = access;

    free(buffer);

    return 0;
}

/* this does not fill the attribs member completely.  additional flags must
   be added in the kernel, such as read-only. */
long uc_stat(ULONG fid, ULONG *attribs, LARGE_INTEGER *size, LARGE_INTEGER *creation,
		LARGE_INTEGER *access, LARGE_INTEGER *change, LARGE_INTEGER *written)
{
    cm_scache_t *scp;
    cm_req_t req;
    ULONG code;

    scp = ifs_FindScp(fid);
    if (!scp)
	return IFSL_BAD_INPUT;

    /* stat file; don't want callback */
    cm_InitReq(&req);
    lock_ObtainMutex(&(scp->mx));
    cm_HoldUser(userp);
    code = cm_SyncOp(scp, NULL, userp, &req, 0, CM_SCACHESYNC_GETSTATUS);
    cm_ReleaseUser(userp);

    if (code)
	lock_ReleaseMutex(&(scp->mx));
    MAP_RETURN(code);

    code = ifs_CopyInfo(scp, attribs, size, creation, access, change, written);
    lock_ReleaseMutex(&(scp->mx));
    MAP_RETURN(code);

    return 0;
}

/* set atime, mtime, etc. */
long uc_setinfo(ULONG fid, ULONG attribs, LARGE_INTEGER creation, LARGE_INTEGER access,
		   LARGE_INTEGER change, LARGE_INTEGER written)
{
    return IFSL_GENERIC_FAILURE;
}

/* FIXFIX: this code may not catch over-quota errors, because the end
 * of the file is not written to the server by the time this returns. */
/* truncate or extend file, in cache and on server */
long uc_trunc(ULONG fid, LARGE_INTEGER size)
{
    ULONG code;
    cm_scache_t *scp;
    cm_req_t req;
    osi_hyper_t oldLen;

    scp = ifs_FindScp(fid);
    if (!scp)
	return IFSL_BAD_INPUT;

    /* we have already checked permissions in the kernel; but, if we do not
     * have access as this userp, code will fail in rpc layer.
     */

    cm_InitReq(&req);
    lock_ObtainMutex(&(scp->mx));

    code = cm_SyncOp(scp, NULL, userp, &req, 0, CM_SCACHESYNC_GETSTATUS);

    if (code)
	lock_ReleaseMutex(&(scp->mx));
    MAP_RETURN(code);

    oldLen = scp->length;
    lock_ReleaseMutex(&(scp->mx));

    code = cm_SetLength(scp, &size, userp, &req);
    MAP_RETURN(code);
    /*code = cm_FSync(scp, userp, &req);
    MAP_RETURN(code);*/
    /*code = cm_SyncOp(scp, NULL, userp, &req, 0, CM_SCACHESYNC_GETSTATUS);
    MAP_RETURN(code);*/

#if 0
    /* attempt to write last byte of file.  fails to bring out quota errors because of delayed writing. */
    if (oldLen.QuadPart < size.QuadPart)
    {
	writePos.QuadPart = size.QuadPart - 1;
	WriteData(scp, writePos, 1, &"\0\0\0", userp, &written);
	MAP_RETURN(code);
	if (written != 1)
            return IFSL_UNSPEC;
    }
#endif

    return 0;
}

/* read data from a file */
long uc_read(ULONG fid, LARGE_INTEGER offset, ULONG length, ULONG *read, char *data)
{
    ULONG code;
    cm_scache_t *scp;

    *read = 0;
 
    scp = ifs_FindScp(fid);
    if (!scp)
	return IFSL_BAD_INPUT;

    if (scp->fileType == CM_SCACHETYPE_DIRECTORY)
	return IFSL_IS_A_DIR;

    code = ReadData(scp, offset, (unsigned long)length, data, userp, read);
    MAP_RETURN(code);

    return 0;
}

/* FIXFIX: this does not catch all overquota errors, because the file
 * is not necessarily written to the server when this returns. */
/* write data to a file */
long uc_write(ULONG fid, LARGE_INTEGER offset, ULONG length, ULONG *written, char *data)
{
    ULONG code;
    cm_scache_t *scp;

    scp = ifs_FindScp(fid);
    if (!scp)
	return IFSL_BAD_INPUT;

    if (offset.QuadPart == -1)
	offset = scp->length;
    code = WriteData(scp, offset, (unsigned long)length, data, userp, written);
    MAP_RETURN(code);

    return 0;
}

long uc_rename(ULONG fid, WCHAR *curr, WCHAR *new_dir, WCHAR *new_name, ULONG *new_fid)
{
    int code;
    cm_req_t req;
    char *curdir, *curfile, *newdir, *newfile;
    cm_scache_t *dscp1, *dscp2, *scp;
    char b1[MAX_PATH], b2[MAX_PATH], b3[MAX_PATH];
    wchar_t b3_w[MAX_PATH];

    code = !(scp = ifs_FindScp(fid));
    if (!code)
	code = ifs_ConvertFileName(curr, -1, b1, MAX_PATH);
    if (!code)
	code = ifs_ConvertFileName(new_name, -1, b2, MAX_PATH);
    if (!code)
	code = ifs_ConvertFileName(new_dir, -1, b3, MAX_PATH);
    if (!code)
    {
	ifs_FindComponents(b1, &curdir, &curfile);
	ifs_FindComponents(b2, &newdir, &newfile);
	newdir = b3;
	uc_close(fid);
	code = cm_NameI(cm_data.rootSCachep, curdir, CM_FLAG_CASEFOLD | CM_FLAG_FOLLOW | CM_FLAG_CHECKPATH, userp, ROOTPATH, &req, &dscp1);
    }
    if (!code)
    {
	if (!strcmp(curdir, newdir))
        {
            dscp2 = dscp1;
            dscp1->refCount++;
        }
	else
            code = cm_NameI(cm_data.rootSCachep, newdir, CM_FLAG_CASEFOLD | CM_FLAG_FOLLOW | CM_FLAG_CHECKPATH, userp, ROOTPATH, &req, &dscp2);
	if (!code)
        {
            code = cm_Rename(dscp1, curfile, dscp2, newfile, userp, &req);
            if (!code)
            {
                strcat(b3, "\\");
                strcat(b3, b2);
		mbstowcs(b3_w, b3, MAX_PATH);
                uc_namei(b3_w, new_fid);
            }
            else
            {
                code = uc_namei(curr, new_fid);
            }
            ifs_InternalClose(&dscp2);
        }
	else
        {
            code = uc_namei(curr, new_fid);
        }
	ifs_InternalClose(&dscp1);
    }

    MAP_RETURN(code);
    return 0;
}

uc_flush(ULONG fid)
{
    ULONG code;
    cm_scache_t *scp;
    cm_req_t req;

    scp = ifs_FindScp(fid);
    if (!scp)
	return IFSL_BAD_INPUT;

    cm_InitReq(&req);
    code = cm_FSync(scp, userp, &req);

    MAP_RETURN(code);
    return 0;
}

ifs_ReaddirCallback(cm_scache_t *scp, cm_dirEntry_t *entry, void *param, osi_hyper_t *offset)
{
    readdir_context_t *context;
    ULONG name_len;
    readdir_data_t *info;
    char short_name[14], *endp;
    ULONG code;
    cm_req_t req;
    cm_scache_t *child_scp;
    cm_fid_t child_fid;
    int t;

    context = param;

    name_len = (ULONG) strlen(entry->name);

    info = (readdir_data_t *)context->buf_pos;
    if (context->length - (context->buf_pos - context->buf) < sizeof(readdir_data_t) + name_len * sizeof(WCHAR) + sizeof(LARGE_INTEGER))
    {
	if (context->count == 0)
            return CM_ERROR_BUFFERTOOSMALL;
	info->cookie = *offset;
	return CM_ERROR_STOPNOW;
    }

    if ((context->matchString && context->matchString[0] && (!strcmp(context->matchString, entry->name) || context->matchString[0]=='*')) ||
         !(context->matchString && context->matchString[0]))
	;       
    else
	return 0;

    cm_InitReq(&req);
    cm_HoldUser(userp);
    child_scp = NULL;

    child_fid.cell = scp->fid.cell;
    child_fid.volume = scp->fid.volume;
    child_fid.vnode = ntohl(entry->fid.vnode);
    child_fid.unique = ntohl(entry->fid.unique);
    code = cm_GetSCache(&child_fid, &child_scp, userp, &req);
    if (code || !child_scp)
    {
	cm_ReleaseUser(userp);
	return 0;
    }

    {
	lock_ObtainMutex(&child_scp->mx);
	code = cm_SyncOp(child_scp, NULL, userp, &req, 0, CM_SCACHESYNC_GETSTATUS | CM_SCACHESYNC_NEEDCALLBACK);
	lock_ReleaseMutex(&child_scp->mx);
    }

    if (code)	/* perhaps blank fields we do not know, and continue.  bad filents should not prevent readdirs. */
	;

    info->cookie = *offset;

    lock_ObtainMutex(&(child_scp->mx));
    code = ifs_CopyInfo(child_scp, &info->attribs, &info->size, &info->creation, &info->access, &info->change, &info->write);
#if 0
	/* make files we do not have write access to read-only */
	/* this is a handy feature, but it takes a lot of time and traffic to enumerate */
    ifs_CheckAcl(child_scp, FILE_WRITE_DATA, &gr);	/* perhaps add flag to not loop, to avoid network traffic if not found*/
    if (gr & FILE_READ_DATA && !(gr & FILE_WRITE_DATA))
    	info->attribs |= FILE_ATTRIBUTE_READONLY;
#endif
    lock_ReleaseMutex(&(child_scp->mx));
    ifs_InternalClose(&child_scp);
    MAP_RETURN(code);

    cm_Gen8Dot3Name(entry, short_name, &endp);
    *endp = '\0';
    info->short_name_length = (CCHAR)sizeof(WCHAR)*((t=MultiByteToWideChar(CP_UTF8, 0, short_name, -1, info->short_name, 14))?t-1:0);
    info->name_length = sizeof(WCHAR)*((t=MultiByteToWideChar(CP_UTF8, 0, entry->name, -1, info->name, 600))?t-1:0);

    context->buf_pos = ((char*)info) + sizeof(readdir_data_t) + info->name_length;
    context->count++;

    info = (readdir_data_t *)context->buf_pos;
    info->cookie.QuadPart = -1;

    return 0;
}

long uc_readdir(ULONG fid, LARGE_INTEGER cookie_in, WCHAR *filter, ULONG *count, char *data, ULONG_PTR *len)
{
    ULONG code;
    char buffer[2048];
    cm_req_t req;
    cm_scache_t *scp;
    readdir_context_t context;
    LARGE_INTEGER cookie;

    if (cookie_in.QuadPart == -1)
    {
	*len = 0;
	*count = 0;
	return 0;
    }

    scp = ifs_FindScp(fid);
    if (!scp)
	return IFSL_BAD_INPUT;
    code = ifs_ConvertFileName(filter, -1, buffer, 2048);
    if (code)
	return code;

    cm_InitReq(&req);
    cm_HoldUser(userp);

    cookie = cookie_in;
    context.matchString = buffer;
    context.buf_pos = context.buf = data;
    context.length = *len;
    context.count = 0;
    *count = 0;

    ((LARGE_INTEGER *)context.buf)->QuadPart = -1;

    code = cm_ApplyDir(scp, ifs_ReaddirCallback, &context, &cookie, userp, &req, NULL);

    context.buf_pos += sizeof(LARGE_INTEGER);

    *count = context.count;

    cm_ReleaseUser(userp);
    *len = context.buf_pos - context.buf;

    code = ifs_MapCmError(code);
    return code;
}

long uc_close(ULONG fid)
{
    cm_scache_t *scp;
    cm_req_t req;
    scp_status_t *prev, *curr;

    scp = ifs_FindScp(fid);
    if (!scp)
	return IFSL_BAD_INPUT;

    cm_InitReq(&req);
    cm_FSync(scp, userp, &req);

    SCPL_LOCK;	/* perhaps this should be earlier */

    lock_ObtainMutex(&(scp->mx));
    cm_ReleaseSCache(scp);
    lock_ReleaseMutex(&(scp->mx));
 
    prev = NULL, curr = scp_list_head;

    while (curr)
    {
	if (curr->fid == fid)
        {
            if (prev)
                prev->next = curr->next;
            else
                scp_list_head = curr->next;
            free(curr);
            break;
        }
	prev = curr;
	curr = curr->next;
    }

    SCPL_UNLOCK;

    return 0;
}

long uc_unlink(WCHAR *name)
{
    char buffer[2048];
    long code;
    cm_scache_t *dscp;
    char *dirp, *filep;
    unsigned char removed;
    cm_req_t req;

    cm_InitReq(&req);

    code = ifs_ConvertFileName(name, -1, buffer, 2048);
    MAP_RETURN(code);
    removed = ifs_FindComponents(buffer, &dirp, &filep);

    if (!(*filep))
	return IFSL_BADFILENAME;

    code = cm_NameI(cm_data.rootSCachep, dirp, CM_FLAG_CASEFOLD | CM_FLAG_FOLLOW | CM_FLAG_CHECKPATH, userp, ROOTPATH, &req, &dscp);
    MAP_RETURN(code);

    code = cm_Unlink(dscp, filep, userp, &req);
    if (code)
	code = cm_RemoveDir(dscp, filep, userp, &req);

    cm_ReleaseSCache(dscp);
    MAP_RETURN(code);

    return 0;
}


long uc_ioctl_write(ULONG length, char *data, ULONG_PTR *key)
{
    smb_ioctl_t *iop;

    iop = malloc(sizeof(smb_ioctl_t));
    memset(iop, 0, sizeof(smb_ioctl_t));
    smb_IoctlPrepareWrite(NULL, iop);

    memcpy(iop->inDatap + iop->inCopied, data, length);
    iop->inCopied += length;
    *key = (ULONG_PTR)iop;

    return 0;
}

long uc_ioctl_read(ULONG_PTR key, ULONG *length, char *data)
{
    smb_ioctl_t *iop;

    iop = (smb_ioctl_t *)key;
    osi_assert(iop);

    cm_HoldUser(userp);
    smb_IoctlPrepareRead(NULL, iop, userp);
    cm_ReleaseUser(userp);

    *length = iop->outDatap - iop->outAllocp;
    memcpy(data, iop->outAllocp, *length);
    free(iop);

    return 0;
}

int ifs_Init(char **reason)
{
    HANDLE kcom;

    kcom = CreateFile("\\\\.\\afscom\\upcallhook", GENERIC_READ | GENERIC_WRITE,
                       FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
                       0, NULL);
    if (kcom == INVALID_HANDLE_VALUE)
    {
	*reason = "error creating communications file";
	return CM_ERROR_REMOTECONN;
    }
    CloseHandle(kcom);

    memset(user_map, 0, MAX_AFS_USERS*sizeof(struct user_map_entry));
    InitializeCriticalSection(&mapLock);
    InitializeCriticalSection(&scp_list_lock);

    return 0;
}

ifs_TransactRpc(char *outbuf, int outlen, char *inbuf, int *inlen)
{
    HANDLE hf;
    DWORD read = 0;
    DWORD inmax;

    if (!outbuf || !inbuf)
	return IFSL_GENERIC_FAILURE;

    hf = CreateFile("\\\\.\\afscom\\downcall", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    if (hf == INVALID_HANDLE_VALUE)
	return 0;

    inmax = *inlen;
    if (!DeviceIoControl(hf, IOCTL_AFSRDR_DOWNCALL, outbuf, outlen, inbuf, inmax, inlen, NULL))
    {
	CloseHandle(hf);
	return IFSL_GENERIC_FAILURE;
    }

    CloseHandle(hf);
    return inlen ? IFSL_SUCCESS : IFSL_GENERIC_FAILURE;
}


DWORD WINAPI ifs_MainLoop(LPVOID param)
{
    HANDLE pipe;
    DWORD written;
    unsigned char *bufIn, *bufOut;
    DWORD lenIn;
    rpc_t rpc;
    BOOL st;

    bufIn = VirtualAlloc(NULL, TRANSFER_BUF_SIZE, MEM_COMMIT, PAGE_READWRITE);
    bufOut = VirtualAlloc(NULL, TRANSFER_BUF_SIZE, MEM_COMMIT, PAGE_READWRITE);
    if (!bufIn || !bufOut)
    {
	if (bufIn) VirtualFree(bufIn, 0, MEM_RELEASE);
	if (bufOut) VirtualFree(bufOut, 0, MEM_RELEASE);
	osi_panic("ifs: allocate transfer buffers", __FILE__, __LINE__);
    }

    pipe = CreateFile("\\\\.\\afscom\\upcallhook", GENERIC_READ | GENERIC_WRITE,
                       FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
                       0, NULL);
    if (pipe == INVALID_HANDLE_VALUE)
    {
	VirtualFree(bufIn, 0, MEM_RELEASE);
	VirtualFree(bufOut, 0, MEM_RELEASE);
	osi_panic("ifs: creating communications handle", __FILE__, __LINE__);
    }

    while (1)
    {
	/* just check if the event is already signalled, do not wait */
	if (WaitForSingleObject(WaitToTerminate, 0) == WAIT_OBJECT_0)
	    break;

	/* read request... */
	st = ReadFile(pipe, bufIn, TRANSFER_BUF_SIZE, &lenIn, NULL);
	if (!st) {
	if (GetLastError() == ERROR_INVALID_HANDLE)
		break;
	else
		continue;
        }

	ZeroMemory(&rpc, sizeof(rpc));
	rpc.in_buf = rpc.in_pos = bufIn;
	rpc.out_buf = rpc.out_pos = bufOut;

	/* ...process it... */
	rpc_parse(&rpc);

	/* ...and write it back */
	st = WriteFile(pipe, rpc.out_buf, rpc.out_pos - rpc.out_buf, &written, NULL);
	if (!st)
	    if (GetLastError() == ERROR_INVALID_HANDLE)
		break;
	    else
		continue;
    }

    return (DWORD)1;
}
