// (c) 1998-2006 Criterion Software Limited and its licensors. All Rights Reserved

#include <string.h>
#include <stdio.h>

#include "realfile.h"
#include "realsystem.h"
#include "bigfile.h"
#include "ifile.h"

//#define TRACE_ENABLE

#ifdef TRACE_ENABLE
#include "debug.h"
#endif

/* Supported operations */
typedef enum {
    FOPTYPE_unused = 0,
    FOPTYPE_used,
    FOPTYPE_open,
    FOPTYPE_close,
    FOPTYPE_read,
    FOPTYPE_write,
    FOPTYPE_size,
    FOPTYPE_null,
    FOPTYPE_exist,
    FOPTYPE_addbig,
    FOPTYPE_delbig,
} FILEOPTYPE;

/* Error messages */
typedef enum {
    FERR_NormalEnd = 0,             /* OK */
    FERR_NoOps,                     /* No more file ops */
    FERR_NoHandles,                 /* No more handles */
    FERR_BigClose,                  /* Attempt to close a file on the bigfile list */
    FERR_BigOpen,                   /* Could not open the bigfile */
    FERR_NoExist,                   /* File does not exist */
    FERR_InvalidHandle,             /* Invalid handle */
    FERR_DelBig,                    /* Attempted to delete a file in a big file */
    FERR_RemoveBig,                 /* There were open files inside the bigfile */
    FERR_NumErrors,
} FILEERR;

/* forward declarations for self-referencing structures */
typedef struct FILEBIG_def          FILEBIG;
typedef struct FILEOPERATION_def    FILEOPERATION;
typedef struct HANDLE_def           HANDLE;
typedef union  OP_def               OP;

struct HANDLE_def
{
    union
    {
        int fh;
        FILEBIG *pBig;              /* for files in a bigfile: the FILEBIG structure which has the real handle */
    } info;
    int length;                     /* the length of the file (in bytes) */
    int bigoffset;                  /* for files in a bigfile: file offset from the beginning of the bigfile */
    int DVDstartsector;             /* the DVD media start sector for this file */
    int checkforEOF;                /* Check for end-of-file and truncate read if longer than file length */
    char name[FILESYS_MAXPATH];     /* the name of the file.  Will need to keep this. */
};

union OP_def {
    struct {
        unsigned    count:20;
        FILEOPTYPE  type:4;
        unsigned    index:8;
    } sep;
    FILEOP          id;
};

struct FILEOPERATION_def {
    OP              op;
    int             cancelled;
    volatile int    status;         /* [-2..1] */
    FILEERR         error;          /* last error code */
    int             priority;
    void            *userdata;
    int             value;          /* flags or offset */
    int             amount;         /* amount of data to read\write */
    void            *data;          /* read\write data buffer */
    HANDLE          *handle;
    FILESYS_CALLBACK   *callback;   /* callback function */
    struct FILEOPERATION_def *next; /* next operation in queue */
};

struct FILEBIG_def {
    void            *header;        /* pointer to the header (dynamically allocated) */
    int             headersize;
    int             memflags;       /* memflags passed to FILESYS_addbig */
    HANDLE          *handle;        /* handle */
    FILEOPERATION   *fop;           /* the operation which is passed back to the user */
    struct FILEBIG_def *next;       /* pointer to the next one in the list */
};

struct {
    int             maxfileops;     /* maximum number of active file ops */
    int             maxopen;        /* maximum number of open files */
    int             minpriority;    /* minimum priority required to start ops (usually FPRI_LOWEST) */
    int             numopsqueued;   /* number of operations waiting to execute */
    volatile int    waitlevel;      /* if we're in a high-level callback, this is > 1. */
    MUTEX           mutex;
    volatile FILEOPERATION * exec;  /* the points to the executing op (NULL if none are executing) */
    FILEOPERATION * oplist;         /* dynamically allocated op array */
    HANDLE *        handlelist;     /* dynamically allocated handle array (*MUST* follow oplist) */
    FILEOPERATION * opqhead;        /* points to the next operation to be started */
    FILEBIG *       bigfilelist;    /* head of the bigfile list. */
} gFileDevice;

static __inline int OPVALID(FILEOP o)
{
    return (o!=0 && (((OP *)&o)->sep.count == gFileDevice.oplist[((OP *)&o)->sep.index].op.sep.count));
}    

// remembers if the memory for the file device was allocated in FILESYS_init
static void *pFileDeviceMem;
static const char *CDdevicename = "cd:";

#ifdef DEBUG
FS_EVNT_CB_INFO fs_evnt_cb_info;
#endif
char currentdirectory[FILESYS_MAXPATH];

/* Prototypes for functions defined in this module */
static void             iFILESYS_ExecCommand(FILEOPERATION *fop);
static void             iFILE_perror(FILEOPERATION *fop);

static FILEOPERATION    *reserveop(void);
static void             freeop(FILEOPERATION *fop);
static HANDLE           *reservehandle(void);
static void             freehandle(HANDLE *handle);

#ifdef DEBUG
static char *FILEERR_string[FERR_NumErrors] = {
    "No error\n",
    "No more fileops\n",
    "Out of handles\n",
    "Bigfile error: attempt to close a file on the bigfile list\n",
    "Bigfile error: could not open the given bigfile\n",
    "Open error: file does not exist\n",
    "Illegal handle\n",
};
#endif

/*
; ABSTRACT
;
; FILESYS_init - initialize the REAL library file system
;@FILESYS_initadr
;
;
; SUMMARY
;
; #include "realfile.h"
;
; bool FILESYS_init    (maxopenfiles, maxfileops)
; bool FILESYS_initadr (maxopenfiles, maxfileops, mem)
;   int maxfiles;           - Maximum number of open file handles.
;   int maxfileops;         - Maximum number of active file operations.
;   char *mem;              - Address to put file system handles, etc.
;
; DESCRIPTION
;
; FILESYS_init uses the REAL library memory manager to allocate some memory,
; and then calls FILE_initadr.  If you plan on reinitializing the memory
; manager during your game, you should reserve the memory for the file system
; outside of the memory manager and pass this memory to FILE_initadr.  Get the
; size of the memory area which needs to be reserved with FILESYS_overhead.
;
; FILESYS_initadr initializes the internal data structures needed for use by
; the file system.  Repeated calls to FILESYS_init/FILESYS_initadr are ignored.
; Passing in 0 for any parameter uses a platform-specific default value.
;
; Please ensure that you have called THREAD_init, TIMER_init before calling
; FILESYS_init.  See the abstract on CDVD_init if you intend to load files off a
; CD or DVD.
;
; Diagnostic information can be obtained using the FILESYS_seteventcb function.
;
; RETURN VALUE
;
; true = success
; false = failure
;
; SEE ALSO
;
; FILESYS_restore, FILESYS_overhead, FILESYS_open, FILESYS_close, 
; FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig,
; FILESYS_setcurrentpath, FILESYS_getcurrentpath, CDVD_init,
; FILESYS_setopts, FILESYS_seteventcb
;
; EXAMPLE
;
; filemem_eg.cpp
;
; END ABSTRACT
*/

#define DEF_MAXOPENFILES    16
#define DEF_MAXFILEOPS      32
#define DEF_MAXDIRFILES     1000

bool FILESYS_init(int maxopenfiles, int maxdirfiles, int maxfileops)
{
#ifdef DEBUG
    if (gFileSysOpts.malloc == 0)
        REAL_abortmessage("FILESYS_init - must set memory callbacks with FILESYS_setopts.\n");
#endif

    if (!gFileDevice.maxfileops)
    {
        pFileDeviceMem = gFileSysOpts.malloc("File Sys", 
            FILESYS_overhead(maxopenfiles, maxdirfiles, maxfileops),
            FILE_ALLOC_HIGHMEM);
        
        return(FILESYS_initadr(maxopenfiles, maxdirfiles, maxfileops, pFileDeviceMem));
    }

    return(false);
}

bool FILESYS_initadr(int maxopenfiles, int maxdirfiles, int maxfileops, void *mem)
{
    int isdiscavail;
    char *aligneddir;

    if (!maxopenfiles)  maxopenfiles = DEF_MAXOPENFILES;
    if (!maxdirfiles)   maxdirfiles = DEF_MAXDIRFILES;
    if (!maxfileops)    maxfileops = DEF_MAXFILEOPS;

#ifdef DEBUG
    if (gFileSysOpts.malloc == 0)
        REAL_abortmessage("FILESYS_initadr - must set memory callbacks with FILESYS_setopts.\n");
#endif

    if (!gFileDevice.maxfileops)
    {
        gFileDevice.maxfileops = maxfileops;
        gFileDevice.maxopen = maxopenfiles;
        gFileDevice.minpriority = FPRI_LOWEST;
        MUTEX_create(&gFileDevice.mutex);
        gFileDevice.oplist = (FILEOPERATION *)mem;

        MEM_fill(gFileDevice.oplist, 0, FILESYS_overhead(maxopenfiles, maxdirfiles, maxfileops));

        gFileDevice.handlelist = (HANDLE *)(gFileDevice.oplist + 
            gFileDevice.maxfileops);

        if (gDiscOpts.usedisc)
        {
            aligneddir = (char *)(gFileDevice.handlelist + gFileDevice.maxopen);
			isdiscavail = CDVD_init(maxdirfiles, aligneddir);
        }
        else
        {
            isdiscavail = 0;
        }

        FILEDEV_init(maxopenfiles, isdiscavail);

        return(true);
    }

    return(false);
}

/*
;
; ABSTRACT
;
; FILESYS_overhead - compute file system overhead
;
; SUMMARY
;
; #include "realfile.h"
;
; int FILESYS_overhead(maxopenfiles, maxdirfiles, maxfileops)
;
; DESCRIPTION
;
; Computes the amount of space needed for open file handles, CD 
; directory information, and file operations.
;
; RETURN VALUE
;
; File system overhead in bytes.
;
; SEE ALSO
;
; FILESYS_init, FILESYS_initadr
;
; END ABSTRACT
;
*/

int FILESYS_overhead(int maxopenfiles, int maxdirfiles, int maxfileops)
{
    if (!maxopenfiles)  maxopenfiles = DEF_MAXOPENFILES;
    if (!maxdirfiles)   maxdirfiles = DEF_MAXDIRFILES;
    if (!maxfileops)    maxfileops = DEF_MAXFILEOPS;

    return(maxfileops * sizeof(FILEOPERATION) + 
        maxopenfiles * (sizeof(HANDLE)) +
        maxdirfiles * SIZEOFPSCDVDFILEINFO + 64);
}

/*
;
; ABSTRACT
;
; FILESYS_restore - shutdown the file system
;
;
; SUMMARY
;
; #include "realfile.h"
;
; void FILESYS_restore (void)
;
; DESCRIPTION
;
; Cancels all outstanding operations, closes all files, terminates all
; threads and deallocates all associated memory.
;
; RETURN VALUE
;
; none
;
; SEE ALSO
;
; FILESYS_init, FILESYS_initadr
;
; EXAMPLE
;
; filemem_eg.cpp, nfile1_eg.cpp
;
; END ABSTRACT
;
*/

void FILESYS_restore(void)
{
    // ensure that the file queue is empty
    REAL_ASSERT(gFileDevice.exec == NULL);

    CDVD_shutdown();

    // remove any open bigfiles
    FILEBIG *pbigfile = gFileDevice.bigfilelist;

    // remove all the open bigfiles from the bigfile list
    while(pbigfile)
    {
        gFileSysOpts.free(pbigfile->header);
        FILESYS_closesync(pbigfile->handle->info.fh, 0);
        pbigfile = pbigfile->next;
    }

    // ensure that all file handles are not in use
#ifdef DEBUG
    for (int i = 0; i < gFileDevice.maxopen; ++i)
    {
        REAL_ASSERT(gFileDevice.handlelist[i].info.fh == 0);
    }
#endif

    MUTEX_destroy(&gFileDevice.mutex);

    MEM_clear(&gFileDevice, sizeof(gFileDevice));

    // if the memory was allocated by FILESYS_init, free it and clear the pointer
    if (pFileDeviceMem != NULL)
    {
        gFileSysOpts.free(pFileDeviceMem);
        pFileDeviceMem = NULL;
    }
}

/*
;
; ABSTRACT
;
; FILESYS_opstatus - return the current status of a FILEOP
;@FILEOP_NOTFINISHED
;@FILEOP_FINISHED
;@FILEOP_CANCELLED
;@FILEOP_FAILED
;@FILEOP_UNKNOWN
;
; SUMMARY
;
; #include "realfile.h"
;
; int FILESYS_opstatus (ophandle)
;   FILEOP      ophandle;       - operation to return the status of
;
; #define FILEOP_NOTFINISHED    - operation has not finished
; #define FILEOP_FINISHED       - operation has finished
; #define FILEOP_CANCELLED      - operation was cancelled
; #define FILEOP_FAILED         - the operation was not successful
; #define FILEOP_UNKNOWN        - the operation is not known
;
; DESCRIPTION
;
; Returns one of the FOP_ status codes for the specified operation.
; Unknown (invalid or completed) operations return a status of
; FILEOP_UNKNOWN.
;
; RETURN VALUE
;
; status of the operation
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; EXAMPLE
; 
; nfile_eg2.c
;
; END ABSTRACT
;
*/

int FILESYS_opstatus(FILEOP ophandle)
{
    if (!OPVALID(ophandle))
    {
        return(FILEOP_UNKNOWN);
    }

    return(gFileDevice.oplist[((OP *)&ophandle)->sep.index].status);
}

/*
;
; ABSTRACT
;
; FILESYS_operror - returns the error associated with the file operation
;
;
; SUMMARY
;
; #include "realfile.h"
;
; int FILESYS_operror (ophandle)
;   FILEOP      ophandle;       - operation to return the status of
;
; DESCRIPTION
;
; Returns an error code describing the error on an operation.  0 
; indicates no error.  Here are the possible error messages:
;
; 1:    All file ops in use
; 2:    Out of file handles
; 3:    Attempt to close a file on the bigfile list
; 4:    Could not open a big file
; 5:    File does not exist
; 6:    Invalid file handle
;
; RETURN VALUE
;
; error code of the operation
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; END ABSTRACT
;
*/

int FILESYS_operror(FILEOP ophandle)
{
#ifdef DEBUG
    if (!OPVALID(ophandle))
    {
        REAL_abortmessage("FILESYS_operror - INVALID FILE HANDLE.\n");
    }
#endif

    return(gFileDevice.oplist[((OP *)&ophandle)->sep.index].error);
}

/*
;
; ABSTRACT
;
; FILESYS_opuserdata - returns user data associated with the file operation
;
;
; SUMMARY
;
; #include "realfile.h"
;
; int FILESYS_opuserdata (ophandle)
;   FILEOP      ophandle;       - operation to get userdata from
;
; DESCRIPTION
;
; When a FILEOP is created one of the parameters is void *userdata.  This
; value is stored with the FILEOP and may be recovered using this function.
; It is usually used to associate context information with a FILEOP.
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; END ABSTRACT
;
*/

void *FILESYS_opuserdata(FILEOP ophandle)
{
#ifdef DEBUG
    if (!OPVALID(ophandle))
    {
        REAL_abortmessage("FILESYS_opuserdata - INVALID FILE HANDLE.\n");
    }
#endif

    return(gFileDevice.oplist[((OP *)&ophandle)->sep.index].userdata);
}

/*
;
; ABSTRACT
;
; FILESYS_callbackop - install a callback in a file operation
;@FILESYS_CALLBACK
;
; SUMMARY
;
; #include "realfile.h"
;
; typedef void  FILESYS_CALLBACK(FILEOP ophandle, int opstatus, void *userdata);
;   - interface to callback function called when the operation completes
;
; int FILESYS_callbackop (ophandle, func)
;   FILEOP        ophandle;     - operation to install into
;   FILESYS_CALLBACK *func;        - function to install
;
; DESCRIPTION
;
; Installs a callback to be made when the specified operation changes
; to the done state.  If already in the done state when this function
; is called, then the callback is made immediately.
;
; Additional calls to the file system may be made from this callback,
; including FILESYS_completeop and any call which starts a new operation.
; In this way a complex series of file operations can be chained together
; and operate asynchronously.
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; END ABSTRACT
;
*/

void FILESYS_callbackop(FILEOP ophandle, FILESYS_CALLBACK *func)
{
    FILEOPERATION *fop = &gFileDevice.oplist[((OP *)&ophandle)->sep.index];

#ifdef DEBUG
    if (!OPVALID(ophandle))
    {
        REAL_abortmessage("FILESYS_callbackop - INVALID FILE HANDLE.\n");
    }
#endif

    fop->callback = func;

    /* if the operation is complete, execute the callback immediately */
    if (fop->status != FILEOP_NOTFINISHED)
    {
        func(ophandle, fop->status, fop->userdata);
    }
}

/*
;
; ABSTRACT
;
; FILESYS_priorityop - change the priority of an op
;@FPRI_HIGHEST
;@FPRI_NORMAL
;@FPRI_LOWEST
;
;
; SUMMARY
;
; #include "realfile.h"
;
; void FILESYS_priorityop (ophandle, priority)
;   FILEOP        ophandle;     - operation to change the priority of
;   int           priority;     - new priority
;
; #define FPRI_HIGHEST    0     - 0 is the highest possible priority
; #define FPRI_NORMAL     100   - 100 is the default priority
; #define FPRI_LOWEST     255   - 255 is the lowest possible priority
;
; DESCRIPTION
;
; Allows the priority of a file operation to be changed.  This is normally
; used to boost the priority of a file operation that has been pending for
; a int time and whose urgency has increased.  If the specified file op
; is no longer pending, no action is taken.
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size,
; FILESYS_exists, FILESYS_addbig, FILESYS_delbig
;
; EXAMPLE
;
; nfile2_eg.cpp
;
; END ABSTRACT
;
*/

void FILESYS_priorityop(FILEOP ophandle, int priority)
{
#ifdef TRACE_ENABLE
    TRACEPOINT tp;
#endif
    int previouspriority;
    FILEOPERATION *fop = &gFileDevice.oplist[((OP *)&ophandle)->sep.index];
    FILEOPERATION *prevop, *currop;

    if (!OPVALID(ophandle))
        return;

#ifdef TRACE_ENABLE
    tp.point = TRACE_FILESYS_PRIORITYOP;
    tp.param[0] = (int)fop;
    tp.param[1] = fop->op.id;
    tp.param[2] = fop->priority;
    tp.param[3] = priority;
    TRACE_add(&tp);
#endif
    MUTEX_lock(&gFileDevice.mutex);

    /* change the priority of this operation */
    previouspriority = fop->priority;
    fop->priority = priority;

    /* there are four conditions under which we may have to move this operation in the chain: */
    if (gFileDevice.numopsqueued >= 2 &&    /* there are at least two operations in the queue, */
        fop != gFileDevice.exec &&          /* this operation is not executing, */
        fop->status == FILEOP_NOTFINISHED &&   /* this opertion is not finished, and */
        previouspriority != priority)       /* the priority actually changed */
    {
        /* find which the operation whose next field points to this operation */
        prevop = NULL;
        currop = gFileDevice.opqhead;
        while (currop && currop != fop)
        {
            prevop = currop;
            currop = currop->next;
        }

        /* abort if the operation wasn't found */
#ifdef DEBUG
        if (!currop)
        {
            MUTEX_unlock(&gFileDevice.mutex);
            REAL_abortmessage("FILESYS_priorityop - INTERNAL ERROR IN OP QUEUE.  OPERATION NOT FOUND.\n");
            return;
        }
#endif

        /* remove this operation from the queue */
        if (prevop)
            prevop->next = fop->next;
        else
            gFileDevice.opqhead = fop->next;

        /* reinsert the operation */
        prevop = NULL;
        fop->next = gFileDevice.opqhead;

        while (fop->next && fop->next->priority <= fop->priority)
        {
            prevop = fop->next;
            fop->next = fop->next->next;
        }

        if (prevop)
            prevop->next = fop;
        else
            gFileDevice.opqhead = fop;
    }

    MUTEX_unlock(&gFileDevice.mutex);
}

/*
;
; ABSTRACT
;
; FILESYS_cancelop - cancels an active file operation
;
;
; SUMMARY
;
; #include "realfile.h"
;
; int FILESYS_cancelop (ophandle)
;   FILEOP      ophandle;       - operation to cancel
;
; DESCRIPTION
;
; Flags an active FILEOP as cancelled.  The operation must still pass through
; the system normally, but any work or change of state that it would have done
; will not happen.  The operation must be completed normally.  The callback,
; if installed, will occur normally.
;
; Compatibility note: if you cancel a read on the PS2, you cannot use the buffer
; again until the operation completes.  This is different behaviour than the
; Playstation 1.  On the PS1, it was safe to reuse the buffer immediately.
;
; Close and delbig operations cannot be cancelled.  If you cancel an operation
; of these types, the request to cancel is ignored.
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; EXAMPLE
;
; nfile2_eg.cpp
;
; END ABSTRACT
;
*/

void FILESYS_cancelop(FILEOP ophandle)
{
#ifdef TRACE_ENABLE
    TRACEPOINT tp;
    tp.point = TRACE_FILESYS_CANCELOP;
    tp.param[0] = (int)ophandle;
    tp.param[1] = ((OP *)ophandle)->id;
    tp.param[2] = gFileDevice.oplist[((OP *)&ophandle)->sep.index].status;
    tp.param[3] = (int)gFileDevice.exec;
    TRACE_add(&tp);
#endif

    if (OPVALID(ophandle) && 
        ((OP *)&ophandle)->sep.type != FOPTYPE_close && 
        ((OP *)&ophandle)->sep.type != FOPTYPE_delbig)
    {
        gFileDevice.oplist[((OP *)&ophandle)->sep.index].cancelled = 1;
    }
}

/*
;
; ABSTRACT
;
; FILESYS_waitop - does not return until the file operation is done
;
;
; SUMMARY
;
; #include "realfile.h"
;
; int FILESYS_waitop (ophandle)
;   FILEOP      ophandle;       - operation to wait for
;
; DESCRIPTION
;
; Will efficiently wait for the specified file operation to be done.
; When this function returns the FILEOP will be in a state where it
; may be completed using FILESYS_completeop, unless the FILEOP was not
; found (in which case FILEOP_UNKNOWN will be returned).
;
; If called from the main thread this function will call SYNCTASK_run.
; Do NOT call this function from a FILEOP callback.
;
; RETURN VALUE
;
; Returns the status of the specified operation after the wait.
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; END ABSTRACT
;
*/

int FILESYS_waitop(FILEOP ophandle)
{
    FILEOPERATION *fop = &gFileDevice.oplist[((OP *)&ophandle)->sep.index];

    if (!OPVALID(ophandle))
        return(FILEOP_UNKNOWN);

    while(fop->status == FILEOP_NOTFINISHED)
    {
        if (THREAD_iscurrent(MAIN_THREAD))
        {
            SYNCTASK_run(0);
        }
        else
        {
            THREAD_yield(1);
        }
        if (!OPVALID(ophandle))
            return(FILEOP_UNKNOWN);
    }

    return(fop->status);
}

/*
;
; ABSTRACT
;
; FILESYS_completeop - cleans up a file operation and returns its result
;
;
; SUMMARY
;
; #include "realfile.h"
;
; int FILESYS_completeop (ophandle)
;   FILEOP      ophandle;       - operation to complete
;
; DESCRIPTION
;
; All FILEOPs must be completed once they are done.  Completing a FILEOP
; before it is done will cause an abort.  Once a FILEOP has been completed
; it no longer exists and no functions will recognize the FILEOP.  If file
; operations are not completed then the system will "leak" FILEOPs and will
; eventually no longer be able to allocate new operations.
;
; FILESYS_completeop may be called from the operation callback.
;
; RETURN VALUE
;
; This call returns the result of the actual file operation being completed.
; What this value means depends on the operation being completed.  For example,
; a FILEOP created by FILESYS_read will return the number of bytes actually read
; from the FILESYS_completeop call.
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; EXAMPLE
;
; nfile2_eg.cpp
;
; END ABSTRACT
;
*/

int FILESYS_completeop(FILEOP ophandle)
{
#ifdef TRACE_ENABLE
    TRACEPOINT tp;
#endif

    FILEOPERATION *fop = &gFileDevice.oplist[((OP *)&ophandle)->sep.index];
    int retval = 0;
    int newlength;

#ifdef DEBUG
    if (!OPVALID(ophandle))
    {
        REAL_abortmessage("FILESYS_completeop - THE OPERATION DOES NOT EXIST.\n");
    }
#endif

    switch(fop->status)
    {
    case FILEOP_NOTFINISHED:
#ifdef DEBUG
        REAL_abortmessage("FILESYS_completeop - THE OPERATION HAS NOT FINISHED YET.\n");
#endif
        break;

    case FILEOP_FINISHED:
    
        switch (fop->op.sep.type)
        {
        case FOPTYPE_open:
        case FOPTYPE_addbig:
            retval = (int)fop->handle;
            break;

        case FOPTYPE_close:
        case FOPTYPE_delbig:
        case FOPTYPE_null:
            retval = fop->status;
            break;

        case FOPTYPE_exist:
        case FOPTYPE_size:
            retval = fop->value;
            break;

        case FOPTYPE_read:
            retval = fop->amount;
            break;

        case FOPTYPE_write:
            retval = fop->amount;
            
            // update the file size and move the EOF marker, if necessary
            newlength = fop->amount /* nbytes */ + fop->value /* offset */;

            if (newlength > fop->handle->length)
                fop->handle->length = newlength;
            break;

        default:
#ifdef DEBUG
            REAL_abortmessage("FILESYS_completeop - UNKNOWN OPERATION.\n");
#endif
            break;
        }
        break;

    case FILEOP_CANCELLED:
    case FILEOP_FAILED:

        // don't leak file handles if the operation was cancelled or failed.
        if (fop->op.sep.type == FOPTYPE_open ||
            fop->op.sep.type == FOPTYPE_addbig)
        {
            freehandle(fop->handle);
        }
        break;
    }
    

#ifdef TRACE_ENABLE
    tp.point = TRACE_FILESYS_COMPLETEOP;
    tp.param[0] = (int)fop;
    tp.param[1] = fop->op.sep.type;
    tp.param[2] = retval;
    TRACE_add(&tp);
#endif

    freeop(fop);
    return(retval);
}

/*
;
; ABSTRACT
;
; FILESYS_exists - starts a file operation to determine if a file exists
;
;
; SUMMARY
;
; #include "realfile.h"
;
; FILEOP FILESYS_exists (name, priority, userdata)
;  char     *name;          - name of the file to check the existance of
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; DESCRIPTION
;
; Determines whether a file exists in the current system with the given
; name.  Names are specified in a platform specific manner.  Any open
; big files will be scanned as well as actual filesystem devices.
;
; This function supports bigfile name qualification as described in
; FILESYS_open.
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return non-zero if the file exists, and zero if
; it doesn't.
;
; SEE ALSO
;
; FILESYS_existssync, FILESYS_completeop, FILESYS_waitop, FILESYS_callbackop,
; FILESYS_opstatus, FILESYS_operror, FILESYS_cancelop, FILESYS_priorityop,
; FILESYS_null, FILESYS_atomic
;
; END ABSTRACT
;
*/

FILEOP FILESYS_exists(const char *name, int priority, void *userdata)
{
    FILEOPERATION *fop = reserveop();

    fop->op.sep.type = FOPTYPE_exist;
    fop->userdata = userdata;
    fop->value = FOPEN_READONLY;
    fop->priority = priority;
    if (!(fop->handle = reservehandle()))
    {
        fop->error = FERR_NoHandles;
        iFILE_perror(fop);
    }
    strncpy(fop->handle->name, (char *)name, FILESYS_MAXPATH);

    iFILESYS_ExecCommand(fop);

    return(fop->op.id);
}

/*
;
; ABSTRACT
;
; FILESYS_open - starts a file operation to open a file
;@REAL_filedevice
;@FILESYS_MAXPATH
;@FOPEN_READONLY
;@FOPEN_CREATE
;@FOPEN_OVERWRITE
;@FOPEN_MEMMAP
;@FOPEN_NOCACHE
;@FOPEN_SEQUENTIAL
;@FILEOP
;
; SUMMARY
;
; #include "realfile.h"
;
; typedef int FILEOP        - Every operation has unique FILEOP handle.
;
; FILEOP FILESYS_open (name, modeflags, priority, userdata)
;  char     *name;          - name of the file to open
;  int      modeflags;      - 0 or some combination of the FOPEN_ defines
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; const char     *REAL_filedevice - file prefix string
;
; DESCRIPTION
;
; Opens the file of the specified name, using the modeflags to control how
; it is opened.  On the Playstation, the only flag which has any effect is 
; FOPEN_READONLY, which allows the open bigfiles to be scanned.  However, 
; FOPEN_CREATE and FOPEN_OVERWRITE are supported if the PC file system is
; being used (i.e. sim:).
;
; The following modeflags are supported:
;
;   FOPEN_READONLY   - open file for reading only
;   FOPEN_CREATE     - create a new file if it doesn`t exist (not with READONLY)
;   FOPEN_OVERWRITE  - delete existing contents of file (not with READONLY)
;   FOPEN_MEMMAP     - ignored on PS2 platform
;   FOPEN_NOCACHE    - ignored on PS2 platform
;   FOPEN_SEQUENTIAL - ignored on PS2 platform
;
; If read-only is specified then the currently opened big files will be
; scanned for the specified file name, if the file name was not found in
; the file system.  File names can be qualified with a big file name to
; scan one or all of the currently open big files and NOT the file system.
; A qualified name has the '|' character prefixed to the file name, and
; an optional big file name before that.  If no big file name is specified
; then all open big files will be scanned.  If a big file name is specified
; then only the specified big file is scanned, and only if it is already
; open.  Examples of qualified names are:
;
;   mybigfilename.viv|myfilename.dat        - searches only the one bigfile
;   |myfilename.dat                         - searches all open bigfiles
;
; Note that if FOPEN_READONLY is not specified then no big files will be
; scanned.  Opening subfiles for modification is not supported.
;
; All sub-files must be closed before the owning big file is closed.
;
; On the Playstation, big file names are case-sensitive, so "ZFLAGS.BIG"
; is not the same as "zflags.big".
;
; REAL_filedevice contains a string which is prefixed to any files opened
; through FILESYS_open.  You can change it to another device during your
; program development.
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return the filehandle of the opened file, or zero
; if the file could not be opened.
;
; SEE ALSO
;
; FILESYS_opensync, FILESYS_completeop, FILESYS_waitop, FILESYS_callbackop,
; FILESYS_opstatus, FILESYS_operror, FILESYS_cancelop, FILESYS_priorityop,
; FILESYS_null, FILESYS_atomic
;
; EXAMPLE
;
; nfile1_eg.cpp
;
; END ABSTRACT
;
*/

FILEOP FILESYS_open(const char *name, unsigned int modeflags, int priority, void *userdata)
{
#ifdef TRACE_ENABLE
    TRACEPOINT tp;
#endif
    FILEOPERATION *fop = reserveop();

    fop->op.sep.type = FOPTYPE_open;
    fop->userdata = userdata;
    fop->value = modeflags;
    fop->priority = priority;
    if (!(fop->handle = reservehandle()))
    {
        fop->error = FERR_NoHandles;
        iFILE_perror(fop);
    }

#ifdef DEBUG
    if (strlen(name) >= FILESYS_MAXPATH-1)
    {
        REAL_abortmessage("FILESYS_open - FILE NAME IS LONGER THAN FILESYS_MAXPATH CHARS.\n");
    }
#endif

    strncpy(fop->handle->name, (char *)name, FILESYS_MAXPATH);

#ifdef TRACE_ENABLE
    tp.point = TRACE_FILESYS_OPEN;
    tp.param[0] = (int)fop;
    tp.param[1] = (int)fop->handle;
    TRACE_add(&tp);
#endif

    iFILESYS_ExecCommand(fop);

    return(fop->op.id);
}

/*
;
; ABSTRACT
;
; FILESYS_close - starts a file operation to close a file
;
;
; SUMMARY
;
; #include "realfile.h"
;
; FILEOP FILESYS_close (filehandle, priority, userdata)
;  int      filehandle;     - handle to the file to be closed
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; DESCRIPTION
;
; Closes an open file.
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return non-zero if successful or zero on failure.
;
; SEE ALSO
;
; FILESYS_closesync, FILESYS_completeop, FILESYS_waitop, FILESYS_callbackop,
; FILESYS_opstatus, FILESYS_operror, FILESYS_null, FILESYS_atomic
;
; EXAMPLE
;
; nfile1_eg.cpp
;
; END ABSTRACT
;
*/

FILEOP FILESYS_close(int filehandle, int priority, void *userdata)
{
#ifdef TRACE_ENABLE
    TRACEPOINT tp;
#endif
    FILEOPERATION *fop = reserveop();
    FILEBIG *pfilebig = gFileDevice.bigfilelist;

    fop->op.sep.type = FOPTYPE_close;
    fop->priority = priority;
    fop->userdata = userdata;
    fop->handle = (HANDLE *)filehandle;

    /* check that this file is not in the list of bigfiles */
    while (pfilebig)
    {
        if (pfilebig->handle == (HANDLE *)filehandle)
        {
#ifdef DEBUG
            REAL_abortmessage("FILESYS_close - you cannot close a file in the bigfile list.\n");
#endif
            fop->error = FERR_BigClose;
            iFILE_perror(fop);
            break;
        }
        pfilebig = pfilebig->next;
    }

#ifdef TRACE_ENABLE
    tp.point = TRACE_FILESYS_CLOSE;
    tp.param[0] = (int)fop;
    tp.param[1] = (int)fop->handle;
    TRACE_add(&tp);
#endif

    iFILESYS_ExecCommand(fop);

    return(fop->op.id);
}

/*
;
; ABSTRACT
;
; FILESYS_read - starts a file operation to read from a file
;
;
; SUMMARY
;
; #include "realfile.h"
;
; FILEOP FILESYS_read (filehandle, offset, buffer, bytes, priority, userdata)
;  int      filehandle;     - handle to the file to be read from
;  int      offset;         - file offset to start reading from
;  void     *buffer;        - data buffer to read into (16-byte aligned)
;  int      bytes;          - amount of data to read
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; DESCRIPTION
;
; Read data from a filehandle at a specified offset.  Note that this file API
; does not maintain an explicit seek position so that every read must specify 
; where in the file it will operate.
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return the number of bytes actually read.
;
; SEE ALSO
;
; FILESYS_readsync, FILESYS_completeop, FILESYS_waitop, FILESYS_callbackop,
; FILESYS_opstatus, FILESYS_operror, FILESYS_cancelop, FILESYS_priorityop,
; FILESYS_null, FILESYS_atomic
;
; nfile2_eg.cpp
;
; END ABSTRACT
;
*/

FILEOP FILESYS_read(int filehandle, int offset, void *buffer, int bytes, int priority, void *userdata)
{
#ifdef TRACE_ENABLE
    TRACEPOINT tp;
#endif
    FILEOPERATION *fop = reserveop();

    fop->op.sep.type = FOPTYPE_read;
    fop->userdata = userdata;
    fop->priority = priority;

    if (!filehandle)
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_read - must supply a file handle.\n");
#endif
        fop->error = FERR_InvalidHandle;
        iFILE_perror(fop);
    }

    fop->handle = (HANDLE *)filehandle;

    if (fop->handle->checkforEOF)
    {
        if (offset+bytes > fop->handle->length)
        {
            bytes = fop->handle->length - offset;
        }
    }

    fop->amount = bytes;
    fop->data = buffer;
    fop->value = offset;

#ifdef TRACE_ENABLE
    tp.point = TRACE_FILESYS_READ;
    tp.param[0] = (int)fop;
    tp.param[1] = filehandle;
    tp.param[2] = bytes;
    tp.param[3] = offset;
    TRACE_add(&tp);
#endif
    
    iFILESYS_ExecCommand(fop);

    return(fop->op.id);
}

/*
;
; ABSTRACT
;
; FILESYS_write - starts a file operation to write to a file
;
;
; SUMMARY
;
; #include "realfile.h"
;
; FILEOP FILESYS_write (filehandle, offset, buffer, bytes, priority, userdata)
;  int      filehandle;     - handle to the file to be written to
;  int      offset;         - file offset to start writing to
;  void     *buffer;        - data buffer to be written
;  int      bytes;          - amount of data to write
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; DESCRIPTION
;
; Write data to a filehandle at a specified offset.  Note that this file API
; does not maintain an explicit seek position so that every write must specify 
; where in the file it will operate.  Writing past the current end of a file
; will extend the length of the file.  On the Playstation, you can only write
; to the PC file system (sim:).
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return the number of bytes actually written.
;
; SEE ALSO
;
; FILESYS_writesync, FILESYS_completeop, FILESYS_waitop, FILESYS_callbackop,
; FILESYS_opstatus, FILESYS_operror, FILESYS_cancelop, FILESYS_priorityop,
; FILESYS_null, FILESYS_atomic
;
; END ABSTRACT
;
*/

FILEOP FILESYS_write(int filehandle, int offset, void *buffer, int bytes, int priority, void *userdata)
{
    FILEOPERATION *fop = reserveop();

    /* may want to check the open mode. */
    fop->op.sep.type = FOPTYPE_write;
    fop->userdata = userdata;
    if (!filehandle)
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_write - must supply a file handle.\n");
#endif
        fop->error = FERR_InvalidHandle;
        iFILE_perror(fop);
    }
    fop->handle = (HANDLE *)filehandle;
    fop->priority = priority;
    fop->amount = bytes;
    fop->value = offset;
    fop->data = buffer;

    iFILESYS_ExecCommand(fop);

    return(fop->op.id);
}

/*
;
; ABSTRACT
;
; FILESYS_size - starts a file operation to get the size of a file
;
;
; SUMMARY
;
; #include "realfile.h"
;
; FILEOP FILESYS_size (filehandle, priority, userdata)
;  int      filehandle;     - handle to the file to be sized
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; DESCRIPTION
;
; Return the current length of the file specified.
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return the length of the file.
;
; SEE ALSO
;
; FILESYS_sizesync, FILESYS_completeop, FILESYS_waitop, FILESYS_callbackop,
; FILESYS_opstatus, FILESYS_operror, FILESYS_cancelop, FILESYS_priorityop,
; FILESYS_null, FILESYS_atomic
;
; END ABSTRACT
;
*/

FILEOP FILESYS_size(int filehandle, int priority, void *userdata)
{
    FILEOPERATION *fop = reserveop();

    fop->op.sep.type = FOPTYPE_size;
    fop->userdata = userdata;
    fop->priority = priority;
#if 0       // do not go through the file queue
    if (!filehandle)
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_size - must supply a file handle.\n");
#endif
        fop->error = FERR_InvalidHandle;
        iFILE_perror(fop);
    }
    fop->handle = (HANDLE *)filehandle;
    iFILESYS_ExecCommand(fop);
#else
    if (filehandle)
    {
        fop->value = ((HANDLE *)filehandle)->length;
        fop->status = FILEOP_FINISHED;
    }
    else
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_size - must supply a file handle.\n");
#endif
        fop->error = FERR_InvalidHandle;
        iFILE_perror(fop);
    }
#endif

    return(fop->op.id);
}

/*
;
; ABSTRACT
;
; FILESYS_null - starts a "placeholder" file operation which does nothing
;
;
; SUMMARY
;
; #include "realfile.h"
;
; FILEOP FILESYS_null (filedevice, priority, userdata)
;  int      filedevice;     - ignored, pass in 0 on Playstation
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; DESCRIPTION
;
; The null operation does nothing, but is useful as a placeholder in the
; stream of FILEOPs on a particular device.  It is used to start a callback
; chain.
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return zero.
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; END ABSTRACT
;
*/

FILEOP FILESYS_null(int filedevice, int priority, void *userdata)
{
    FILEOPERATION *fop = reserveop();

    fop->op.sep.type = FOPTYPE_null;
    fop->userdata = userdata;
    fop->priority = priority;

    iFILESYS_ExecCommand(fop);

    return(fop->op.id);
}


/*
;
; ABSTRACT
;
; FILESYS_addbig - starts a file operation to add a new big file to the 
;               active chain of open big files
;
;
; SUMMARY
;
; #include "realfile.h"
;
; FILEOP FILESYS_addbig (name, memflags, priority, userdata)
;  char     *name;          - name of big file to add
;  int      memflags;       - memtype parameter for bigfile data structure
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; DESCRIPTION
;
; Opens a new big file and adds it into the bigfile search chain.
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return bighandle to the newly added big file.  If
; the bigfile cannot be opened or added zero will be returned.
;
; The Playstation implementation of FILESYS_addbig loads in the header
; (asynchronously of course), but you must remember that it will have to
; use the memory manager to allocate a buffer for the bigfile header.
; Because the memory manager is not thread-safe on the Playstation, you 
; must not use the memory manager until this function has completed.
;
; SEE ALSO
;
; FILESYS_addbigsync, FILESYS_delbig, FILESYS_completeop, FILESYS_waitop, FILESYS_callbackop,
; FILESYS_opstatus, FILESYS_operror, FILESYS_cancelop, FILESYS_priorityop, FILESYS_null, 
; FILESYS_atomic
;
; EXAMPLE
;
; nfile1_eg.cpp
;
; END ABSTRACT
;
*/

void iFILE_addbigreadcallback(FILEOP ophandle, int opstatus, void *userdata)
{
    FILEBIG *pfilebig = (FILEBIG *)userdata;
    FILEOPERATION *fop = &gFileDevice.oplist[((OP *)&ophandle)->sep.index];
    int priority = fop->priority;
    int bigheadersize;

    pfilebig->fop->handle = pfilebig->handle = fop->handle;
    FILESYS_completeop(ophandle);

    if (!BIG_typeofheader(pfilebig->header))
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_addbig - file %s is not recognized as big file.\n", pfilebig->handle->name);
#endif
        gFileSysOpts.free(pfilebig->header);
    }

    bigheadersize = BIG_sizeofheader(pfilebig->header);

    if (bigheadersize > pfilebig->headersize)
    {
        gFileSysOpts.free(pfilebig->header);
        pfilebig->headersize = bigheadersize;
        pfilebig->header = gFileSysOpts.malloc("bigfile buf", pfilebig->headersize,
            pfilebig->memflags);

        ophandle = FILESYS_read((int)pfilebig->handle, 0, pfilebig->header, bigheadersize,
            priority, pfilebig);
        FILESYS_callbackop(ophandle, iFILE_addbigreadcallback);
    }
    else
    {
        /* insert this bigfile into the bigfile list */
        pfilebig->next = gFileDevice.bigfilelist;
        gFileDevice.bigfilelist = pfilebig;

        /* unlock the revservemem mutex */
        iFILESYS_ExecCommand(pfilebig->fop);
    }
}

void iFILE_addbigopencallback(FILEOP ophandle, int opstatus, void *userdata)
{
    FILEBIG *pfilebig = (FILEBIG *)userdata;
    int priority = gFileDevice.oplist[((OP *)&ophandle)->sep.index].priority;
    int handle = FILESYS_completeop(ophandle);

    if (opstatus != FILEOP_FINISHED)
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_addbig - could not open bigfile.\n");
#endif
        pfilebig->fop->error = FERR_BigOpen;
        iFILESYS_ExecCommand(pfilebig->fop);
        return;
    }

    /* read the header */
    ophandle = FILESYS_read(handle, 0, pfilebig->header, MEDIA_SECTOR_SIZE, priority, pfilebig);
    FILESYS_callbackop(ophandle, iFILE_addbigreadcallback);
}

FILEOP FILESYS_addbig(const char *name, int memflags, int priority, void *userdata)
{
    FILEBIG *pfilebig = gFileDevice.bigfilelist;
    FILEOPERATION *fop = reserveop();
    FILEOP op;
    
    fop->op.sep.type = FOPTYPE_addbig;
    fop->userdata = userdata;
    fop->priority = priority;

    /* if the big file is already in the list, tell it to go ahead and execute. */
    /* the execute thread for addbig just marks the the op complete. */
    while (pfilebig)
    {
        if (!strncmp((char *)name, pfilebig->handle->name, FILESYS_MAXPATH))
        {
            fop->handle = pfilebig->handle;
            iFILESYS_ExecCommand(fop);
            return((FILEOP)fop);
        }
        pfilebig = pfilebig->next;
    }

    /* reserve a block of memory for the FILEBIG struct and fill it in. */
    pfilebig = (FILEBIG *)gFileSysOpts.malloc(name, sizeof(FILEBIG), memflags);
    pfilebig->fop = fop;
    pfilebig->headersize = MEDIA_SECTOR_SIZE;
    pfilebig->memflags = memflags;
    pfilebig->header = gFileSysOpts.malloc("bigfile buf", pfilebig->headersize, memflags);
#ifdef DEBUG
    if (!pfilebig->header)
    {
        REAL_abortmessage("FILESYS_addbig - COULD NOT ALLOCATE MEMORY FOR BIGFILE HEADER.\n");
    }
#endif

    op = FILESYS_open(name, FOPEN_READONLY, priority, pfilebig);
    FILESYS_callbackop(op, iFILE_addbigopencallback);

    return(fop->op.id);
}

/*
;
; ABSTRACT
;
; FILESYS_delbig - starts a file operation to remove a bigfile from the chain
;
;
; SUMMARY
;
; #include "realfile.h"
;
; FILEOP FILESYS_delbig (bighandle, priority, userdata)
;  int      bighandle;      - handle to the bigfile to be removed
;  int      priority;       - priority of the operation
;  void     *userdata;      - application data to associate with this op
;
; DESCRIPTION
;
; Removes an open bigfile from the bigfile search chain.
;
; RETURN VALUE
;
; This function returns the FILEOP created.  When the FILEOP is completed,
; FILESYS_completeop will return non-zero if the operation succeeds, zero if
; the bighandle cannot be found.
;
; SEE ALSO
;
; FILESYS_delbigsync, FILESYS_addbig, FILESYS_completeop, FILESYS_waitop, FILESYS_callbackop,
; FILESYS_opstatus, FILESYS_operror, FILESYS_null, FILESYS_atomic
;
; EXAMPLE
;
; nfile1_eg.cpp
;
; END ABSTRACT
;
*/

void iFILE_delbigclosecallback(FILEOP ophandle, int opstatus, void *userdata)
{
    FILESYS_completeop(ophandle);

    iFILESYS_ExecCommand((FILEOPERATION *)userdata);    /* the userdata is the fop returned from FILESYS_delbig */
}


FILEOP FILESYS_delbig(int bighandle, int priority, void *userdata)
{
    FILEBIG *prevfilebig = NULL, *pfilebig = gFileDevice.bigfilelist;
    FILEOPERATION *fop = reserveop();
    HANDLE *handle = gFileDevice.handlelist;
    FILEOP op;
    int i;

    fop->op.sep.type = FOPTYPE_delbig;
    fop->userdata = userdata;
    fop->priority = priority;

#ifdef DEBUG
    if (!bighandle)
    {
        REAL_abortmessage("FILESYS_delbig - invalid big file handle (0x%p).\n", (void *)bighandle);
    }
#endif

    /* check that is not a file in a big file */
    if (((HANDLE *)bighandle)->bigoffset)
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_delbig - CANNOT DELETE FILES IN BIG FILES.\n");
#endif
        fop->error = FERR_DelBig;
    }

    /* check that there are no open files in this big file */
    for (i = 0; i < gFileDevice.maxopen; i++, handle++)
    {
        if (handle && handle->bigoffset && handle->info.pBig->handle == (HANDLE *)bighandle)
        {
#ifdef DEBUG
            REAL_abortmessage("FILESYS_delbig - there are open files in this big file.  Close them first.\n");
#endif
            fop->status = FILEOP_FAILED;
            fop->error = FERR_RemoveBig;
            break;
        }
    }

    /* remove this big file from the list */
    while (pfilebig && pfilebig->handle != (HANDLE *)bighandle)
    {
        prevfilebig = pfilebig;
        pfilebig = pfilebig->next;
    }

    if (!pfilebig)
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_delbig - handle (0x%x) was not found in the big file list.\n",
            bighandle);
#endif
        fop->error = FERR_NoExist;
    }

    if (prevfilebig)
        prevfilebig->next = pfilebig->next;
    else
        gFileDevice.bigfilelist = gFileDevice.bigfilelist->next;

    gFileSysOpts.free(pfilebig->header);
    gFileSysOpts.free(pfilebig);

    /* close the file */
    op = FILESYS_close(bighandle, priority, fop);
    FILESYS_callbackop(op, iFILE_delbigclosecallback);

    return(fop->op.id);
}

/*
;
; ABSTRACT
;
; FILESYS_getbigheader - gets a pointer to the header of a big file.
;
;
; SUMMARY
;
; #include "realfile.h"
;
; void* FILESYS_getbigheader( int bighandle )
;  int      bighandle;      - handle to the bigfile
;
; DESCRIPTION
;
; Given a valid big file handle, returns a pointer to
; the header of a big file.
;
; RETURN VALUE
;
; SEE ALSO
;
; EXAMPLE
; END ABSTRACT
;
*/
REALAPI void*  FILESYS_getbigheader( int bighandle )
{
    FILEBIG *pfilebig = gFileDevice.bigfilelist;
    void* vpResult = NULL;

#ifdef DEBUG
    if (!bighandle)
    {
        REAL_abortmessage("FILESYS_getbigheader - invalid big file handle (0x%p).\n", (void *)bighandle);
    }
#endif
    
    /* get this big file from the list */
    while (pfilebig && pfilebig->handle != (HANDLE *)bighandle)
    {
        pfilebig = pfilebig->next;
    }

    if (!pfilebig)
    {
#ifdef DEBUG
        REAL_abortmessage("FILESYS_getbigheader - handle (0x%x) was not found in the big file list.\n",
            bighandle);
#endif
    }
    else
    {
        vpResult = pfilebig->header;
    }

    return vpResult;
}

/*
;
; ABSTRACT
;
; FILESYS_atomic - performs a user callback with lower priorities locked out
;@FILESYS_ATOM
;
; SUMMARY
;
; #include "realfile.h"
;
; typedef int   FILESYS_ATOM(int priority, void *userdata);
;   - interface to callback function called when the operation completes
;
; int FILESYS_atomic (func, filedevice, priority, userdata)
;  FILESYS_ATOM *func;         - user callback
;  int        filedevice;   - file system device identifier
;  int        priority;     - priority of the operation
;  void      *userdata;     - application data to associate with this op
;
; DESCRIPTION
;
; This function performs a user callback function while preventing lower
; priority FILEOPs from executing on the specified device.
;
; This function is typically used to execute a series of operations which 
; will not be interrupted by lower priority operations.  Example:  performing
; an open, read, close sequence on a file while preventing low priority 
; stream ops to the same device causing unnecessary streaming.
;
; RETURN VALUE
;
; This function returns the return value from the func callback.
;
; SEE ALSO
;
; FILESYS_open, FILESYS_close, FILESYS_read, FILESYS_write, FILESYS_size, FILESYS_exists, 
; FILESYS_addbig, FILESYS_delbig
;
; nfile1_eg.cpp
;
; END ABSTRACT
;
*/

int FILESYS_atomic(FILESYS_ATOM *func, int filedevice, int priority, void *userdata)
{
    int retval, prevminpriority = gFileDevice.minpriority;

#ifdef DEBUG
    if (gFileDevice.minpriority < priority)
    {
        REAL_abortmessage("FILESYS_atomic - CALLED AT PRIORITY (%d) LOWER THAN CURRENT MINIMUM PRIORITY (%d).\n",
            priority, gFileDevice.minpriority);
    }
#endif

    gFileDevice.minpriority = priority;

    retval = func(priority, userdata);

    gFileDevice.minpriority = prevminpriority;

    /* start the next command */
    iFILESYS_ExecCommand(NULL);

    return(retval);
}

/*
;
; ABSTRACT
;
; FILESYS_setminpriority - set a minimum priority level for file ops to run at
;
;
; SUMMARY
;
; #include "realfile.h"
;
; void FILESYS_setminpriority(int priority)
;   priority - minimum priority level to run at
;
; DESCRIPTION
;
; FILESYS_setminpriority is a hack, and should not be used.  It will be removed
; in REAL 5.0.  See FILESYS_atomic instead.
;
; SEE ALSO
;
; FILESYS_atomic
;
; END ABSTRACT
;
*/

void FILESYS_setminpriority(int priority)
{
    gFileDevice.minpriority = priority;

    // signal the command queue because if the minimum priority was just
    // decreased (a larger number) then there might be some operations which
    // can be started.
    iFILESYS_ExecCommand(NULL);
}

/*
;
; ABSTRACT
;
; FILESYS_setcurrentpath - change directories/filesystems
; FILESYS_getcurrentpath - get the current directory
;
;
; SUMMARY
;
; #include "realfile.h"
;
; void FILESYS_setcurrentpath(const char *path)
; void FILESYS_getcurrentpath(char *path)
;   path - a NULL-terminated string which is the new working directory.
;   
; DESCRIPTION
;
; Sets or gets the default directory path.  Applicable on the host file
; system or on the CD/DVD file system.  When setting the currentpath, you
; may include a device name ("cd:" or "host0:") in the path.  The maximum
; length of a path (not including the device name) is FILESYS_MAXPATH
; characters, defined in realfile.h.
;
; FILESYS_getcurrentpath does not copy the device name into the path
; buffer.
;
; For example, suppose the file server root directory is "d:\prj\frapi"
;
;   command                                 current directory
;   -------                                 -----------------
; FILESYS_setcurrentpath("host0:");         d:\prj\frapi
; FILESYS_setcurrentpath("data");           d:\prj\frapi\data
; FILESYS_setcurrentpath("/demo/files");    d:\demo\files
; FILESYS_setcurrentpath("cd:");            root directory of the CD/DVD
; FILESYS_setcurrentpath("cd:/data");       /data directory of the CD/DVD
;
; SEE ALSO
;
; FILESYS_init
;
; END ABSTRACT
;
*/

void FILESYS_setcurrentpath(const char *path)
{
    int namelen = strlen(REAL_filedevice);
    int currentdirectorylength;

    /* if a device name ("host0:" or "cd:"), update currentfilesystem */
    if(!strncmp(path, (char *)CDdevicename, 3))
    {
        /* it is possible that FILEDEV_init was not called before changing
         * to the CD/DVD.  Abort only if the PCFS is available and the CDFS
         * is not available.
         */
        if ((availablefilesystems & PCFS) && !(availablefilesystems & CDFS))
        {
#ifdef DEBUG
            REAL_abortmessage("setdirectory - DISC IS NOT AVAILABLE.\n");
#endif
            return;
        }

        currentfilesystem = CDFS;
        path += 3;
    }
    else if (!strncmp(path, REAL_filedevice, namelen))
    {
        if (!(availablefilesystems & PCFS))
        {
#ifdef DEBUG
            REAL_abortmessage("setdirectory - PC FILE SYSTEM IS NOT AVAILABLE.\n");
#endif
            return;
        }

        currentfilesystem = PCFS;
        path += namelen;
    }

    strcpy(currentdirectory, path);

    // ensure the path name is not too int
    if (currentdirectory[FILESYS_MAXPATH-1] != '\0')
    {
#ifdef DEBUG
        REAL_abortmessage("setdirectory - PATH NAME TOO LONG (%s).  MAX IS %d CHARS.\n", 
            path, FILESYS_MAXPATH);
#endif
        return;
    }

    currentdirectorylength = strlen(currentdirectory);

    // put a trailing slash on the current directory if necessary
    if (currentdirectorylength > 0 && 
        currentdirectory[currentdirectorylength-1] != '/')
    {
        strcat(currentdirectory, "/");
    }
}

void FILESYS_getcurrentpath(char *path)
{
    strncpy(path, currentdirectory, FILESYS_MAXPATH);
}

/*
;
; UNABSTRACT
;
; void iFILESYS_ExecCommand(FILEOPERATION *fop)
;
;
; DESCRIPTION
;
; Adds the operation to the queue, and then starts the next operation if the
; the read device is not busy.
;
; This function is similar to the Windows' version of the thread server.
;
; END UNABSTRACT
;
*/

static void iFILESYS_ExecCommand(FILEOPERATION *fop)
{
    FILEOPERATION *prevop;
    char bigfilenamebuf[FILESYS_MAXPATH];
    char smallfilenamebuf[FILESYS_MAXPATH];
    int fSearch;
    int bFound;

#ifdef TRACE_ENABLE
    TRACEPOINT tp;

    tp.point = TRACE_FILESYS_EXECCMD_ADD;
    tp.param[0] = (int)fop;
    if (fop)
    {
        tp.param[1] = fop->op.id;
        tp.param[2] = fop->priority;
    }
    else
    {
        tp.param[1] = 0;
        tp.param[2] = 0;
    }
    tp.param[3] = (int)gFileDevice.exec;
    TRACE_add(&tp);
#endif

    MUTEX_lock(&gFileDevice.mutex);

    // if an operation was supplied, insert it into the queue.
    if (fop)
    {
        prevop = NULL;
        fop->next = gFileDevice.opqhead;

        // sort by:
        // 1: priority
        // 2: DVD start sector + offset
        // 3: if subfile inside bigfile, DVDstartsector + bigoffset + offset
        while (fop->next && fop->next->priority <= fop->priority)
        {
            prevop = fop->next;
            fop->next = fop->next->next;
        }

        if (prevop)
            prevop->next = fop;
        else
            gFileDevice.opqhead = fop;

        gFileDevice.numopsqueued++;
    }

    // if an operation is already executing, or we are waiting for a callback to complete, return.
    if (gFileDevice.exec || gFileDevice.waitlevel)
    {
        MUTEX_unlock(&gFileDevice.mutex);
        return;
    }

    // get the next item from the queue
    fop = gFileDevice.opqhead;
    if (fop && fop->priority <= gFileDevice.minpriority)
    {
        gFileDevice.opqhead = fop->next;
        gFileDevice.numopsqueued--;
    }
    else
        fop = NULL;

    gFileDevice.exec = fop;
    MUTEX_unlock(&gFileDevice.mutex);

    if (!fop)
    {
        return;
    }
    
#ifdef TRACE_ENABLE
    tp.point = TRACE_FILESYS_EXECCMD_EXEC;
    tp.param[0] = (int)fop;
    tp.param[1] = fop->op.id;
    tp.param[2] = fop->priority;
    tp.param[3] = fop->cancelled;
    tp.param[4] = gFileDevice.numopsqueued;
    TRACE_add(&tp);
#endif

    if (fop->cancelled)
    {
        iFILESYS_CommandCompleteCallback(0);
        return;
    }

    switch(fop->op.sep.type)
    {
    case FOPTYPE_open:
    case FOPTYPE_exist:
        fSearch = 0;
        smallfilenamebuf[0] = '\0';

#ifdef DEBUG
        if (fs_evnt_cb_info.pCbFn != NULL)
        {
            fs_evnt_cb_info.pCbFn(
                fop->op.sep.type == FOPTYPE_open ? FS_EVNT_OPEN : FS_EVNT_AS_EXIST,
                fop->handle->name, fs_evnt_cb_info.userdata);
        }
#endif

        if (strchr(fop->handle->name, '|'))
        {
            if (fop->handle->name[0] == '|')
            {
                fSearch = 0x2;  /* search all big files */
            }
            else
            {
                int bigfilenamelen = strchr(fop->handle->name, '|') - fop->handle->name;

                bigfilenamebuf[0] = '\0';
                strncpy(bigfilenamebuf, fop->handle->name, bigfilenamelen);
                bigfilenamebuf[bigfilenamelen] = '\0';
                fSearch = 0x4;  /* search one bigfile */
            }
            strcpy(smallfilenamebuf, strchr(fop->handle->name, '|')+1);
        }
        else
        {
            fSearch = 0x1;     /* search file system */
            if (((fop->value & FOPEN_READONLY) != 0) &&
                ((fop->value & FOPEN_IGNOREBIGFILES) == 0))
            {
                fSearch |= 0x2;/* search all bigfiles */
                strcpy(smallfilenamebuf, fop->handle->name);
            }
        }

        bFound = 0;
        if (fSearch & 0x1)
        {
            if (FILEDEV_open(fop->handle->name, fop->value, &fop->handle->info.fh))
            {
                /* the file was found, get the size and return success. */
                bFound = 1;
                if (fop->op.sep.type == FOPTYPE_exist)
                {
                    FILEDEV_close(fop->handle->info.fh);
                }
                else
                {
                    fop->handle->length = FILEDEV_getsize(fop->handle->info.fh);
                    fop->handle->DVDstartsector = FILEDEV_getstartsector(fop->handle->info.fh);
                }
            }
        }

        /* if the file was not found, and we are searching one or more bigfiles */
        if (!bFound && fSearch & 0x6)
        {
            FILEBIG *pfilebig = gFileDevice.bigfilelist;
            int len, offset;

            while (pfilebig && !bFound)
            {
                /* if we are searching for a specific bigfile and this is not it, */
                /* capitialization may matter. */
                if (fSearch & 0x4 && strcmp(pfilebig->handle->name, bigfilenamebuf))
                {
                    pfilebig = pfilebig->next;
                    continue;
                }

                if (BIG_locateentryz(pfilebig->header, smallfilenamebuf, 0, &offset, &len))
                {
                    /* found; fill in the relevant fields. */
                    fop->handle->info.pBig = pfilebig;
                    fop->handle->length = len;
                    fop->handle->bigoffset = offset;
                    bFound = 1;

#ifdef DEBUG
					if (fs_evnt_cb_info.pCbFn != NULL)
					{
						FS_EVNT_LOCATE_INFO locate_info;

						locate_info.bigname = pfilebig->handle->name;
						locate_info.name = smallfilenamebuf;
						locate_info.handle = (int)fop->handle;
						locate_info.ofs = offset;
						locate_info.nbytes = len;

						fs_evnt_cb_info.pCbFn(FS_EVNT_LOCATEBIG, &locate_info, fs_evnt_cb_info.userdata);
					}
#endif
                }
				
				pfilebig = pfilebig->next;
            }
        }

        if (bFound)
        {
            fop->handle->checkforEOF = ((fop->value & FOPEN_NOEOFCHECK) == 0);
        }

        if (fop->op.sep.type == FOPTYPE_exist)
        {
            freehandle(fop->handle);
            fop->value = bFound;
        }

        iFILESYS_CommandCompleteCallback(bFound);
        break;
    
    case FOPTYPE_close:
        fop->error = FERR_NoOps;     /* this is the return value */           
#ifdef DEBUG
        if (fs_evnt_cb_info.pCbFn != NULL)
        {
            fs_evnt_cb_info.pCbFn(FS_EVNT_CLOSE, &fop->handle, fs_evnt_cb_info.userdata);
        }
#endif
        if (fop->handle)
        {
            /* if this is not a bighandle, close the file */
            if (!fop->handle->bigoffset)
            {
                fop->error = (FILEERR)FILEDEV_close(fop->handle->info.fh);
            }
            else
            {
                fop->error = FERR_NormalEnd;     /* this is the return value */
            }
            freehandle(fop->handle);
            fop->handle = NULL;
        }

        iFILESYS_CommandCompleteCallback(!fop->error);
        break;
    
    case FOPTYPE_read:
        if (fop->amount > 0)
        {
#ifdef DEBUG
            if (fs_evnt_cb_info.pCbFn != NULL)
            {
                FS_EVNT_AS_READ_INFO as_info;

                as_info.name = fop->handle->name;
                as_info.handle = (int)fop->handle;                
                as_info.ofs = fop->value;
                as_info.nbytes = fop->amount;
                as_info.raddr = fop->data;

                fs_evnt_cb_info.pCbFn(FS_EVNT_AS_READ, &as_info, fs_evnt_cb_info.userdata);
            }
#endif
            if (fop->handle->bigoffset)
            {
                FILEDEV_readreq(fop->handle->info.pBig->handle->info.fh, fop->data, 
                    fop->handle->bigoffset+fop->value, fop->amount);
            }
            else
            {
                FILEDEV_readreq(fop->handle->info.fh, fop->data, fop->value, fop->amount);
            }
        }
        else
        {
            iFILESYS_CommandCompleteCallback(1);
        }

        break;
    
    case FOPTYPE_write:
        FILEDEV_write(fop->handle->info.fh, fop->data, fop->value, fop->amount);
        break;

    case FOPTYPE_size:
        fop->value = fop->handle->length;
        iFILESYS_CommandCompleteCallback(1);
        break;

    case FOPTYPE_addbig:
    case FOPTYPE_delbig:
    case FOPTYPE_null:
        /* When this is called, we know the operation is complete. */
        iFILESYS_CommandCompleteCallback(!fop->error);
        break;

    default:
        break;
    }
}

/*
;
; UNABSTRACT
;
; void iFILESYS_CommandCompleteCallback(int result)
;
;
; DESCRIPTION
;
; This function is called when an operation completes.  If the result is 1, the op
; status is set to FILEOP_FINISHED, otherwise it is set to FILEOP_FAILED.  This function
; also checks the cancelled field, and if this flag is set, the status is set to 
; cancelled.  If a callback function has been set, the callback is executed.
;
; Finally, iFILESYS_ExecCommand is called to start any operations which were queued 
; while the current op was executing, but only if we are not already in a callback.
; If we are currently in a high-level callback we don't want to check the queue yet 
; for any pending requests.  The callback may be at a higher level than the other 
; items on the queue, and we don't want to give up control to a lower-priority 
; operation until the callback function has completed.  When all callbacks are done, 
; iFILESYS_ExecCommand(NULL) is called to start any pending operations.  If the 
; callback installed another command, it will already be executing, and the lower-
; priority operation won't get a chance to go until the currently executing 
; operation has completed.
;
; END UNABSTRACT
;
*/

void iFILESYS_CommandCompleteCallback(int result)
{
    FILEOPERATION *fop;
#ifdef TRACE_ENABLE
    TRACEPOINT tp;
#endif

    gFileDevice.waitlevel++;

    fop = (FILEOPERATION *)gFileDevice.exec;

    if (!fop)
    {
#ifdef DEBUG
        REAL_abortmessage("INTERNAL LIBRARY ERROR.  GOT A COMMAND COMPLETE WITH NO EXECUTING OPERATION.\n");
#endif
        return;
    }

#ifdef TRACE_ENABLE
    tp.point = TRACE_FILESYS_CMDCMPLETE;
    tp.param[0] = (int)fop;
    tp.param[1] = result;
    tp.param[2] = (int)fop->callback;
    tp.param[3] = gFileDevice.waitlevel;
    TRACE_add(&tp);
#endif

#ifdef DEBUG
    if (fop->status != FILEOP_NOTFINISHED)
    {
        REAL_abortmessage("INTERNAL LIBRARY ERROR.  STATUS AT COMMAND COMPLETE WAS OTHER THAN NOTFINISHED. (%d)\n", 
            fop->status);
    }
#endif

    if (fop->cancelled)
    {
        fop->status = FILEOP_CANCELLED;
    }
    else
    {
#ifdef DEBUG
        if (fs_evnt_cb_info.pCbFn != NULL)
        {
            fs_evnt_cb_info.pCbFn(FS_EVNT_AS_CMDCOMPL, NULL,
                fs_evnt_cb_info.userdata);
        }
#endif
        fop->status = result ? FILEOP_FINISHED : FILEOP_FAILED;
    }

    gFileDevice.exec = NULL;

    // execute the callback
    if (fop->callback)
    {
        fop->callback(fop->op.id, fop->status, fop->userdata);
    }

    // if we're on the last callback, start the next operation.
    gFileDevice.waitlevel--;
    if (gFileDevice.waitlevel == 0)
    {
        iFILESYS_ExecCommand(NULL);
    }
}

/*
;
; ABSTRACT
;
; FILESYS_seteventcb - set up a function to trace what's happening on the CD
;@FS_EVNT
;@FS_EVNT_OPEN
;@FS_EVNT_CLOSE
;@FS_EVNT_AS_READ
;@FS_EVNT_AS_WRITE
;@FS_EVNT_AS_CMDCOMPL
;@FS_EVNT_CD_READ
;@FS_EVNT_CD_READCOMPL
;@FS_EVNT_AS_READ_INFO
;@FS_EVNT_CD_READ_INFO
;
; SUMMARY
;
; #include "realfile.h"
;
; void FILESYS_seteventcb(void (*handler)(FS_EVNT event, void *param, void *userdata),
;                         void *userdata);
;   void (*handler)(FS_EVNT event, void *param, void *userdata)
;            - pointer to a function to be called when an event occurs.  The
;              event tells you which event occurred.  Some events use the param
;              for passing back extra data, and the userdata parameter is the
;              same as the userdata parameter passed to FILESYS_seteventcb.  It
;              can be NULL or anything you want.
;              
;   userdata - pointer which will be passed to the handler function in the
;              userdata parameter
;   
; typedef enum FS_EVNT                  - event types
; {
;     FS_EVNT_OPEN,         - a file was opened.  param contains the name
;     FS_EVNT_CLOSE,        - a file was closed.  param contains the handle
;     FS_EVNT_AS_READ,      - a read was scheduled.  param is a pointer to an 
;                             FS_EVNT_AS_READ_INFO struct.
;     FS_EVNT_AS_WRITE,     - a write was scheduled.
;     FS_EVNT_AS_CMDCOMPL,  - a command completed
;     FS_EVNT_CD_READ,      - a CD read was started.  param is a pointer to an
;                             FS_EVNT_CD_READ_INFO struct.
;     FS_EVNT_CD_READCOMPL  - the CD read completed.
; } FS_EVNT;
;
; typedef struct FS_EVNT_AS_READ_INFO   - filesys async read info
; {
;     char *name;           - name of file
;     int handle;           - handle
;     int ofs;              - offset at which the read was requested
;     int nbytes;           - number of bytes requested
;     void *raddr;          - address to which the data will be copied
; } FS_EVNT_AS_READ_INFO;
; 
; typedef struct FS_EVNT_CD_READ_INFO   - low-level media read info
; {
;     char *name;           - name of file
;     int sector;           - starting sector
;     int num_sectors;      - number of sectors
; } FS_EVNT_CD_READ_INFO;
;   
; DESCRIPTION
;
; Sets an event callback function.  Although the function is present in the non-
; debug version of the library, it is just a stub there.  In the debug library,
; when certain interesting events happen, you can find out about them by setting
; an event callback function to be called when they occur.  You can then use this
; information to improve the layout of the files on the CD to reduce seek times,
; and potentially find other poor use of the file system library.
;
;
; RETURN VALUE
;
; none
;
; EXAMPLE
;
; fs_event_eg.cpp
;
; SEE ALSO
;
; FILESYS_init
;
; END ABSTRACT
;
*/

void FILESYS_seteventcb(void (*handler)(FS_EVNT event, void *param, void *userdata),
                        void *userdata)
{
#ifdef DEBUG
    fs_evnt_cb_info.pCbFn = handler;
    fs_evnt_cb_info.userdata = userdata;
#endif
}

/*
;
; ABSTRACT
;
; FILESYS_bypassqueuefileinfo - quickly get the size of a file (PS2 only)
;
;
; SUMMARY
;
; #include "realfile.h"
;
; int FILESYS_bypassqueuefileinfo(name, modeflags, size)
;   char *name          - name of file to search for
;   unsigned int flags  - 0 or FOPEN_READONLY
;   int *size           - (output) pointer size of the file (0 if not found)
;   
;   
; DESCRIPTION
;
; Checks for the existence of a file and retrieves its size without going
; through the serialization queue that FILESYS_size and FILESYS_open go
; through.  The file is not left in an opened state.  This is useful if
; there may be a stream running in the background which could cause calls
; to FILE_size or FILE_exist to take a long time to return.  The list of
; active big files is checked, as well as the file system.  All the same
; flags as those passed to FILESYS_open are respected.
;
; Note that the size pointer may NOT be NULL.  It must point to a valid
; address.  If the file is not found, 0 will be returned in this parameter.
;
; RETURN VALUE
;
; If the file was found - 1.  If not, 0.
;
; SEE ALSO
;
; FILESYS_open
;
; EXAMPLE
;
; streamer_eg.cpp
;
; END ABSTRACT
;
*/

bool FILESYS_bypassqueuefileinfo(const char *name, unsigned int modeflags, int *size)
{
    char bigfilenamebuf[FILESYS_MAXPATH];
    char smallfilenamebuf[FILESYS_MAXPATH];
    bool bFound = false;
    int fSearch = 0;
    int handle;

    *size = 0;

    smallfilenamebuf[0] = '\0';
    if (strchr(name, '|'))
    {
        if (name[0] == '|')
        {
            fSearch = 0x2;  // search all big files
        }
        else
        {
            int bigfilenamelen = strchr(name, '|') - name;

            strncpy(bigfilenamebuf, name, bigfilenamelen);
            bigfilenamebuf[bigfilenamelen] = '\0';
            fSearch = 0x4;  // search one bigfile
        }
        strcpy(smallfilenamebuf, strchr(name, '|')+1);
    }
    else
    {
        fSearch = 0x1;     // search file system
        if (((modeflags & FOPEN_READONLY) != 0) &&
            ((modeflags & FOPEN_IGNOREBIGFILES) == 0))
        {
            fSearch |= 0x2;// search all bigfiles
            strcpy(smallfilenamebuf, name);
        }
    }

    if (fSearch & 0x1)
    {
        if (FILEDEV_open(name, modeflags, &handle))
        {
            // the file was found, get the size and return success.
            bFound = 1;
            *size = FILEDEV_getsize(handle);

            FILEDEV_close(handle);
        }
    }

    // if the file was not found, and we are searching one or more bigfiles
    if (!bFound && fSearch & 0x6)
    {
        FILEBIG *pfilebig = gFileDevice.bigfilelist;
        int len, offset;

        while (pfilebig && !bFound)
        {
            // if we are searching for a specific bigfile and this is not it,
            // capitialization may matter.
            if (fSearch & 0x4 && strcmp(pfilebig->handle->name, bigfilenamebuf))
            {
                pfilebig = pfilebig->next;
                continue;
            }

            if (BIG_locateentryz(pfilebig->header, smallfilenamebuf, 0, &offset, &len))
            {
                // found; fill in the relevant fields.
                *size = len;
                bFound = 1;
            }
            pfilebig = pfilebig->next;
        }
    }

    return(bFound);
}

static void iFILE_perror(FILEOPERATION *fop)
{
#ifdef DEBUG
    puts(FILEERR_string[fop->error]);
#endif
}

/* reserve\free file ops */
static FILEOPERATION *reserveop(void)
{
    int i;
    static int opcount = 1;

    MUTEX_lock(&gFileDevice.mutex);

    /* find a free fileop */
    for(i = 0; i < gFileDevice.maxfileops; i++)
    {
        if (gFileDevice.oplist[i].op.sep.type == FOPTYPE_unused)
        {
            gFileDevice.oplist[i].op.sep.type = FOPTYPE_used;
            gFileDevice.oplist[i].op.sep.index = i;
            gFileDevice.oplist[i].op.sep.count = opcount++;

            // when the counter wraps, instead of setting the opcount
            // to zero, set it to 1.  This will ensure that we cannot
            // ever get a condition where OPVALID returns true when
            // the operation in question is the first one in the array
            // and it gets freed on another thread and no other operations
            // can be queued until the current one completes.
            // FILESYS_waitop could hang as a result.
            if (opcount >= (1 << 20))
                opcount = 1;
            break;
        }
    }

    MUTEX_unlock(&gFileDevice.mutex);

    /* abort if no more available ops */
    if ( i == gFileDevice.maxfileops )
    {
#ifdef DEBUG
        REAL_abortmessage("Out of file ops.\n");
#endif
        // hang the system
        for(;;);
    }

    return(&gFileDevice.oplist[i]);
}

static void freeop(FILEOPERATION *fop)
{
    MUTEX_lock(&gFileDevice.mutex);
    MEM_fill(fop,0, sizeof(*fop));
    MUTEX_unlock(&gFileDevice.mutex);
}

static HANDLE *reservehandle(void)
{
    int i;
    MUTEX_lock(&gFileDevice.mutex);

    /* find a free handle */
    for(i = 0; i < gFileDevice.maxopen; i++)
    {
        if (!gFileDevice.handlelist[i].info.fh)
        {
            gFileDevice.handlelist[i].info.fh = 1;
            break;
        }
    }

    MUTEX_unlock(&gFileDevice.mutex);

    /* abort if no more available handles */
    if ( i == gFileDevice.maxopen )
    {
#ifdef DEBUG
        REAL_abortmessage("Out of file handles.\n");
#endif
        // hang the system
        for(;;);
    }

    return(&gFileDevice.handlelist[i]);
}

static void freehandle(HANDLE *handle)
{
    MUTEX_lock(&gFileDevice.mutex);
    MEM_fill(handle,0, sizeof(*handle));
    MUTEX_unlock(&gFileDevice.mutex);
}

#ifdef DEBUG

/*
;
; UNABSTRACT
;
; void FILE_printinfo(void)
;
; DESCRIPTION
;
; Dumps some FILE state information to the print console.  Useful for diagnostics.
;
; END UNABSTRACT
;
*/

void FILE_printinfo(void)
{
    int i, openfiles;
    FILEBIG *pfilebig = gFileDevice.bigfilelist;
    FILEOPERATION *fop;

    printf("FILE module information dump:\n");
    if (!gFileDevice.maxopen)
    {
        printf("FILE module has not been initialized yet.\n");
        return;
    }

    printf("Open files:\n");
    for (openfiles = i = 0; i < gFileDevice.maxopen; i++)
    {
        if (gFileDevice.handlelist[i].info.fh)
        {
            /* if this file is a file in a bigfile, print the name of the parent bigfile too. */
            if (gFileDevice.handlelist[i].bigoffset)
            {
                printf("File %s in bigfile %s at offset %d\n", gFileDevice.handlelist[i].name, 
                    gFileDevice.handlelist[i].info.pBig->handle->name,
                    gFileDevice.handlelist[i].bigoffset);
            }
            else
            {
                printf("File %s\n", gFileDevice.handlelist[i].name);
            }
            openfiles++;
        }
    }
    printf("There are %d file(s) open out of a maximum of %d.\n\n", openfiles, gFileDevice.maxopen);

    printf("Big files:\n");
    while(pfilebig)
    {
        printf("%s with %d files in it.\n", pfilebig->handle->name, BIG_count(pfilebig->header));
        pfilebig = pfilebig->next;
    }

    printf("\nFile ops:\n");
    for (i = 0; i < gFileDevice.maxfileops; i++)
    {
        fop = gFileDevice.oplist+i;

        if (fop == gFileDevice.exec)
        {
            printf("The following op is currently executing:\n");
        }

        switch(gFileDevice.oplist[i].op.sep.type)
        {
        case FOPTYPE_unused:
            printf("FILEOP %d is not used.\n", i);
            break;
        case FOPTYPE_open:
            printf("open: %s, priority: %d\n", (char *)fop->handle->name, fop->priority);
            break;

        case FOPTYPE_close:
            printf("close: %s, priority: %d\n", fop->handle->name, fop->priority);
            break;

        case FOPTYPE_read:
            printf("read: %s, offset: %d, bytes: %d, priority: %d\n", fop->handle->name,
                fop->value, fop->amount, fop->priority);
            break;

        case FOPTYPE_write:
        case FOPTYPE_size:
            printf("size: %s, priority: %d\n", fop->handle->name, fop->priority);
            break;

        case FOPTYPE_exist:
            break;

        case FOPTYPE_addbig:
            printf("addbig: %s, priority: %d\n", fop->handle->name, fop->priority);
            break;

        case FOPTYPE_delbig:
            printf("delbig: %s, priority: %d\n", fop->handle->name, fop->priority);
            break;

        default:
            break;
        }
    }

    printf("\nDump complete.\n\n");
}
#endif

/*
;
; ABSTRACT
;
; FILESYS_setmemcallbacks - deprecated.  set functions for file system memory management
;@FILE_ALLOC_HIGHMEM
;
;
; SUMMARY
;
; #include "realfile.h"
;
; void FILESYS_setmemcallbacks(void * (*pfnFILE_malloc)(const char *name, int size, int flags),
;                            int (*pfnFILE_mfree)(void *memadr))
;   pfnFILE_malloc  - allocator function
;   pfnFILE_mfree   - deallocator function
;
; #define FILE_ALLOC_HIGHMEM      0x100     - flag passed to allocator to request
;                                             the memory block be allocated from
;                                             the high end of the heap to reduce
;                                             fragmentation
;
; DESCRIPTION
;
; FILESYS_setmemcallbacks is deprecated as of REAL 5.06.  Use FILESYS_setopts
; instead.
;
; The file library requires a memory manager.  If you want to use your own,
; these function pointers can be set to your own allocator and deallocator
; functions.
;
; The paramters to pfnFILE_malloc are:
; name  - a NULL-terminated character string which is description of the block being
;         allocated (this can be ignored)
; size  - the size of the memory block requested
; flags - a set of flags (this can be ignored)
;
; Your allocator function should return the address to be used, or NULL if the
; memory block cannot be allocated.
;
; The parameter to pfnFILE_mfree is the memory block to be freed.  If the
; deallocation was successful, return 1, otherwise return 0.
;
; SEE ALSO
;
; FILESYS_init, REALFILE_init, REAL_init
;
; EXAMPLE
;
; filemem_eg.cpp
;
; END ABSTRACT
;
*/

void FILESYS_setmemcallbacks(void * (*pfnFILE_malloc)(const char *, int, int),
                             bool (*pfnFILE_mfree)(void *))
{
    gFileSysOpts.malloc = pfnFILE_malloc;
    gFileSysOpts.free = pfnFILE_mfree;
}

