/* 
 * fsStream.c --
 *
 *	The  procedures handle the mapping from streams to
 *	user-level stream IDs,	which are indexes into a per-process array
 *	of stream pointers.
 *
 * Copyright (C) 1987 Regents of the University of California
 * All rights reserved.
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies.  The University of California
 * makes no representations about the suitability of this
 * software for any purpose.  It is provided "as is" without
 * express or implied warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /r3/kupfer/spriteserver/src/sprited/fs/RCS/fsTopStream.c,v 1.2 91/12/01 21:58:15 kupfer Exp $ SPRITE (Berkeley)";
#endif not lint


#include <sprite.h>
#include <ckalloc.h>
#include <fs.h>
#include <fsutil.h>
#include <fsio.h>
#include <fsNameOps.h>
#include <fsconsist.h>
#include <fsStat.h>
#include <proc.h>
#include <sync.h>
#include <rpc.h>


/*
 * Forward declarations. 
 */
static ReturnStatus GrowStreamList _ARGS_((Fs_ProcessState *fsPtr, 
					int newLength));


/*
 *----------------------------------------------------------------------
 *
 * Fs_GetStreamID --
 *
 *	Save the stream pointer in the process's list of stream pointers
 *	and return its index in that list.  The index is used as
 *	a handle for the stream.  E.g. the user supplies the index
 *	in read and write calls and the kernel gets the file pointer
 *	from the list.
 *
 * Results:
 *	SUCCESS		- the operation was successful.
 *	others		- value returned by GrowStreamList.
 *
 * Side effects:
 *	It adds the input streamPtr to the end of the process's list,
 *	if the list is too short (or empty) it is expanded (or created).
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fs_GetStreamID(streamPtr, streamIDPtr)
    Fs_Stream	*streamPtr;	/* A reference to an open file */
    int		*streamIDPtr;	/* Return value, the index of the file pointer
				 * in the process's list of open files */
{
    register Fs_ProcessState	*fsPtr;		/* From process's proc table 
						 * entry */
    register Fs_Stream		**streamPtrPtr;	/* Process's list of open 
						 * streams. */
    register int		index;		/* Index into list of open 
						 * streams */
    ReturnStatus		status;		/* Error from growing file 
						 * list. */

    fsPtr = (Proc_GetEffectiveProc())->fsPtr;

    if (streamPtr == (Fs_Stream *)0) {
	panic( "Zero valued streamPtr");
    }
    if (fsPtr->streamList == (Fs_Stream **)NIL) {
	/*
	 * Allocate the initial array of file pointers.
	 */
	(void)GrowStreamList(fsPtr, 8);
    }

    /*
     * Take the first free streamID, or add a new one to the end.
     */
    for (index = 0, streamPtrPtr = fsPtr->streamList; 
	 index < fsPtr->numStreams; 
	 index++, streamPtrPtr++) {
	if (*streamPtrPtr == (Fs_Stream *)NIL) {
	    *streamPtrPtr = streamPtr;
	    *streamIDPtr = index;
	    fsPtr->streamFlags[index] = 0;
	    return(SUCCESS);
	}
    }
    /*
     * Ran out of room in the original array, allocate a larger
     * array, copy the contents of the original into the beginning,
     * then pick the first empty slot.
     */
    index = fsPtr->numStreams;
    status = GrowStreamList(fsPtr, fsPtr->numStreams * 2);
    if (status == SUCCESS) {
	*streamIDPtr = index;
	fsPtr->streamList[index] = streamPtr;
	fsPtr->streamFlags[index] = 0;
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * Fs_ClearStreamID --
 *
 *	This invalidates a stream ID.  This is called in conjuction
 *	with Fs_Close to close a stream.  The open stream is identified
 *	by the stream ID which this routine invalidates.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The stream pointer entry in the open stream list is cleared.
 *
 *----------------------------------------------------------------------
 */
void
Fs_ClearStreamID(streamID, procPtr)
    int streamID;		/* Stream ID to invalidate */
    Proc_ControlBlock *procPtr;	/* (Optional) process pointer */
{
    if (procPtr == (Proc_ControlBlock *)NIL) {
	procPtr = Proc_GetEffectiveProc();
    }
    procPtr->fsPtr->streamList[streamID] = (Fs_Stream *)NIL;
}


/*
 *----------------------------------------------------------------------
 *
 * GrowStreamList --
 *
 *	Grow a stream ID list.  This routine
 *	allocates another array of file pointers and copies the
 *	values from the original array into the new one.  It also
 *	initializes the new array elements to NIL.  The original
 *	array of pointers is free'd and the pointer to the
 *	array is reset to point to the new array.
 *
 * Results:
 *	SUCCESS		- the operation was successful.
 *	(One could limit the number of streams here, but we don't.)
 *
 * Side effects:
 *	Grows the stream list and the associated array of flag bytes.
 *	The number of streams in the file system state is updated.
 *
 *----------------------------------------------------------------------
 */
static ReturnStatus
GrowStreamList(fsPtr, newLength)
    Fs_ProcessState *fsPtr;	/* The file system state */
    int newLength;		/* The length of the new array */
{
    register int index;
    register Fs_Stream **streamList;
    register char *streamFlags;

    streamList = (Fs_Stream **)ckalloc(newLength * sizeof(Fs_Stream *));
    streamFlags = (char *)ckalloc(newLength * sizeof(char));

    if (fsPtr->numStreams > 0) {
	bcopy((Address)fsPtr->streamList, (Address)streamList, sizeof(Fs_Stream *) * fsPtr->numStreams);
	bcopy((Address)fsPtr->streamFlags, (Address)streamFlags, sizeof(char) * fsPtr->numStreams);

	ckfree((Address)fsPtr->streamList);
	ckfree((Address)fsPtr->streamFlags);
    
	for (index=0 ; index < fsPtr->numStreams ; index++) {
	    if ((int)streamList[index] != NIL &&
		(unsigned int)streamList[index] < 1024) {
		panic( "GrowStreamList copied bad streamPtr, %x\n",
				       streamList[index]);
	    }
	}
    }

    fsPtr->streamList = streamList;
    fsPtr->streamFlags =  streamFlags;

    for (index=fsPtr->numStreams ; index < newLength ; index++) {
	fsPtr->streamList[index] = (Fs_Stream *)NIL;
	fsPtr->streamFlags[index] = 0;
    }
    fsPtr->numStreams = newLength;
    return(SUCCESS);
}


/*
 *----------------------------------------------------------------------
 *
 * Fs_GetStreamPtr --
 *
 *	This converts a users stream id into a pointer to the
 *	stream structure for the open stream.  The stream id is
 *	an index into a per-process open stream list.  This does
 *	bounds checking the open stream list and returns the
 *	indexed stream pointer.
 *
 * Results:
 *	SUCCESS		- the operation was successful.
 *	FS_INVALID_ARG	- the stream ID was out of range or the streamPtr
 *			  for streamID was NIL.
 *
 * Side effects:
 *	*streamPtrPtr is set to reference the stream structure indexed
 *	by the streamID.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fs_GetStreamPtr(procPtr, streamID, streamPtrPtr)
    Proc_ControlBlock	*procPtr;	/* The owner of an open file list */
    int			streamID;	/* A possible index into the list */
    Fs_Stream		**streamPtrPtr;	/* The pointer from the list*/
{
    if (streamID < 0 || streamID >= procPtr->fsPtr->numStreams) {
	return(FS_INVALID_ARG);
    } else {
	register Fs_Stream *streamPtr;

	streamPtr = procPtr->fsPtr->streamList[streamID];
	if (streamPtr == (Fs_Stream *)NIL) {
	    return(FS_INVALID_ARG);
	} else if ((unsigned int)streamPtr < 1024) {
	    /*
	     * There was a time when control stream pointers were not
	     * being passed right, or being passed a second time after
	     * already being converted to a streamID, which resulted in
	     * small integers being kept in the stream list instead of
	     * valid stream pointers.  Not sure if that still happens.
	     */
	    panic( "Stream Ptr # %d was an int %d!\n",
				streamID, streamPtr);
	    return(FS_INVALID_ARG);
	} else {
	    *streamPtrPtr = streamPtr;
	    return(SUCCESS);
	}
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Fs_GetNewID --
 *
 *	This gets a new stream ID that refers to the same open file
 *	as the first argument.  After the call the new stream ID will
 *	be equivalent to the old one - system calls that take a stream
 *	ID could be passed either stream ID.  There are two uses of this
 *	routine.  If it doesn't matter what the new stream ID is then
 *	the second argument should point to FS_ANYID.  If the new stream
 *	ID should have a value then the second argument should point
 *	to that value.  If that value was a valid stream ID then the
 *	stream is first closed.
 *
 * Results:
 *	SUCCESS		- the operation was successful.
 *	FS_INVALID_ARG	- newStreamIDPtr was bad or had a bad value.
 *	other		- value returned by FsGrowList.
 *
 * Side effects:
 *	The second argument gets instantiated to a new stream ID.
 *	If the second argument refered to a valid stream ID on entry
 *	(as opposed to pointing to FS_ANYID) then that stream is first closed.
 *
 *----------------------------------------------------------------------
 */
ReturnStatus
Fs_GetNewID(streamID, newStreamIDPtr)
    int streamID;
    int *newStreamIDPtr;
{
    register ReturnStatus 	status;
    Fs_Stream 			*streamPtr;
    Proc_ControlBlock		*procPtr;
    register Fs_ProcessState	*fsPtr;

    if (newStreamIDPtr == (int *)NIL) {
	return(FS_INVALID_ARG);
    }
    procPtr = Proc_GetEffectiveProc();
    status = Fs_GetStreamPtr(procPtr, streamID, &streamPtr);
    if (status != SUCCESS) {
	return(status);
    }
    fsPtr = procPtr->fsPtr;
    if (*newStreamIDPtr == FS_ANYID) {
	Fs_Stream		*newStreamPtr;

	Fsio_StreamCopy(streamPtr, &newStreamPtr);
	status = Fs_GetStreamID(newStreamPtr, newStreamIDPtr);
	if (status != SUCCESS) {
	    (void)Fs_Close(newStreamPtr);
	}
	return(status);
    } else {
	if (*newStreamIDPtr == streamID) {
	    /*
	     * Probably a user error.  We just return without fiddling
	     * with reference counts.
	     */
	    return(SUCCESS);
	} else {
	    /*
	     * Trying to get a specific stream ID.
	     */
	    register int newStreamID;

	    newStreamID = *newStreamIDPtr;
	    if (newStreamID < 0) {
		return(FS_INVALID_ARG);
	    }
	    if (newStreamID >= fsPtr->numStreams) {
		register int maxID;
		/*
		 * Need to grow the file list to accomodate this stream ID.
		 * We do a sanity check on the value of stream ID so
		 * we don't nuke ourselves with a huge array.
		 */
		maxID = 2 * fsPtr->numStreams;
		maxID = (maxID<128 ? 128 : maxID);
		if (newStreamID > maxID) {
		    return(FS_NEW_ID_TOO_BIG);
		}
		status = GrowStreamList(fsPtr, newStreamID + 1 );
		if (status != SUCCESS) {
		    return(status);
		}
	    } else {
		/*
		 * Check to see if *newStreamIDPtr is a valid (Fs_Stream *)
		 * and close it if it is.
		 */
		register Fs_Stream *oldFilePtr;

		oldFilePtr = fsPtr->streamList[newStreamID];
		if (oldFilePtr != (Fs_Stream *)NIL) {
		    (void)Fs_Close(oldFilePtr);
		}
	    }
	    Fsio_StreamCopy(streamPtr, &fsPtr->streamList[newStreamID]);
	    return(SUCCESS);
	}
    }
}
