/* 
 * ofsIo.c --
 *
 *	Routines providing I/O for OFS domain files.
 *
 * Copyright 1990 Regents of the University of California
 * 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: /sprite/src/kernel/ofs/RCS/ofsIo.c,v 1.4 91/09/24 11:55:35 mendel Exp $ SPRITE (Berkeley)";
#endif /* not lint */

#include <sprite.h>
#include <fs.h>
#include <fsutil.h>
#include <fslcl.h>
#include <fsNameOps.h>
#include <fsio.h>
#include <fsStat.h>
#include <fsdm.h>
#include <fscache.h>
#include <ofs.h>
#include <devBlockDevice.h>

#include <stdio.h>

static Sync_Lock ofsCleanerLock = Sync_LockInitStatic("Fs:ofsCleanerLock");
#define	LOCKPTR	&ofsCleanerLock

static int ofsBlockCleaners = 0; /* Number of block cleaner processes in 
				  * action. */

/*
 * ofsBlockWritesPerFile - maximum number of blocks written for a file
 * before switching to the next dirty file.
 */
int	ofsBlockWritesPerFile = 25;

extern void Ofs_CleanBlocks _ARGS_((ClientData data, Proc_CallInfo *callInfoPtr));
static Boolean FileMatch _ARGS_((Fscache_FileInfo *cacheInfoPtr, 
				ClientData clientData));


/*
 *----------------------------------------------------------------------
 *
 * Ofs_FileBlockRead --
 *
 *	Read in a cache block.  This does a direct disk read if the
 *	file is the 'physical file' used for file descriptors and
 *	indirect blocks.  If it is a regular file data block, then
 *	the indexing structure is used to locate the file on disk.
 *	This always attempts to read in a full block, but will read
 *	less if at the last block and it isn't full.  In this case,
 *	the remainder of the cache block is zero-filled.
 *
 * Results:
 *	The results of the disk read.
 *
 * Side effects:
 *	The buffer is filled with the number of bytes indicated by
 *	the bufSize parameter.  The blockPtr->blockSize is modified to
 *	reflect how much data was actually read in.  The unused part
 *	of the block is filled with zeroes so that higher levels can
 *	always assume the block has good stuff in all parts of it.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
Ofs_FileBlockRead(domainPtr, handlePtr, blockPtr)
    Fsdm_Domain		*domainPtr;	/* Domain of file. */
    register Fsio_FileIOHandle *handlePtr;	/* Handle on a local file. */
    Fscache_Block	*blockPtr;	/* Cache block to read in.  This assumes
					 * the blockNum, blockAddr (buffer area)
					 * and blockSize are set.  This modifies
					 * blockSize if less bytes were read
					 * because of EOF. */
{
    register Ofs_Domain		*ofsPtr = OFS_PTR_FROM_DOMAIN(domainPtr);
    register	Fsdm_FileDescriptor *descPtr;
    register			 offset;
    register int		 numBytes;
    ReturnStatus		 status;
    OfsBlockIndexInfo		 indexInfo;

    status = SUCCESS;
    blockPtr->blockSize = 0;
    numBytes = FS_BLOCK_SIZE;
    offset = blockPtr->blockNum * FS_BLOCK_SIZE;

    if (handlePtr->hdr.fileID.minor == 0) {
	/*
	 * If is a physical block address then read it in directly.
	 */
	status = OfsDeviceBlockIO(ofsPtr, FS_READ, 
			   offset / FS_FRAGMENT_SIZE, FS_FRAGMENTS_PER_BLOCK, 
			   blockPtr->blockAddr);
	fs_Stats.gen.physBytesRead += FS_BLOCK_SIZE;
    } else {
	/*
	 * Is a logical file read. Round the size down to the actual
	 * last byte in the file.
	 */

	descPtr = handlePtr->descPtr;
	if (offset > descPtr->lastByte) {
	    numBytes = 0;
	    goto exit;
	} else if (offset + numBytes - 1 > descPtr->lastByte) {
	    numBytes = descPtr->lastByte - offset + 1;
	}

	status = OfsGetFirstIndex(ofsPtr, handlePtr, offset / FS_BLOCK_SIZE, 
				 &indexInfo, 0);
	if (status != SUCCESS) {
	    printf("Ofs_FileRead: Could not setup indexing\n");
	    goto exit;
	}

	if (indexInfo.blockAddrPtr != (int *) NIL &&
	    *indexInfo.blockAddrPtr != FSDM_NIL_INDEX) {
	    /*
	     * Read in the block.  Specify the device, the fragment index,
	     * the number of fragments, and the memory buffer.
	     */
	    status = OfsDeviceBlockIO(ofsPtr, FS_READ, 
		      *indexInfo.blockAddrPtr +
		      ofsPtr->headerPtr->dataOffset * FS_FRAGMENTS_PER_BLOCK,
		      (numBytes - 1) / FS_FRAGMENT_SIZE + 1,
		      blockPtr->blockAddr);
	} else {
	    /*
	     * Zero fill the block.  We're in a 'hole' in the file.
	     */
	    fs_Stats.blockCache.readZeroFills++;
	    bzero(blockPtr->blockAddr, numBytes);
	}
	OfsEndIndex(handlePtr, &indexInfo, FALSE);
	Fs_StatAdd(numBytes, fs_Stats.gen.fileBytesRead,
		   fs_Stats.gen.fileReadOverflow);
#ifdef SOSP91
	if (proc_RunningProcesses[0] != (Proc_ControlBlock *) NIL) {
	    if ((proc_RunningProcesses[0]->state == PROC_MIGRATED) ||
		    (proc_RunningProcesses[0]->genFlags &
		    (PROC_FOREIGN | PROC_MIGRATING))) {
		Fs_StatAdd(numBytes, fs_SospMigStats.gen.fileBytesRead, 
			    fs_SospMigStats.gen.fileReadOverflow);
	    }
	}
#endif SOSP91
    }
exit:
    /*
     * Define the block size and error fill leftover space.
     */
    if (status == SUCCESS) {
	blockPtr->blockSize = numBytes;
    }
    if (blockPtr->blockSize < FS_BLOCK_SIZE) {
	fs_Stats.blockCache.readZeroFills++;
	bzero(blockPtr->blockAddr + blockPtr->blockSize,
		FS_BLOCK_SIZE - blockPtr->blockSize);
    }
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * Ofs_FileBlockWrite --
 *
 *	Write out a cache block.  This understands about physical
 *	block writes as opposed to file block writes, and it understands
 *	that negative block numbers are used for indirect blocks (gag).
 *	Physical blocks are numbered from the beginning of the disk,
 *	and they are used for file descriptors and indirect blocks.
 *	File blocks are numbered from the beginning of the data block
 *	area, so an offset must be used to calculate their true address.
 *
 * Results:
 *	The return code from the driver, or FS_DOMAIN_UNAVAILABLE if
 *	the domain has been un-attached.
 *
 * Side effects:
 *	The device write.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
Ofs_FileBlockWrite(domainPtr, handlePtr, blockPtr)
    register	Fsdm_Domain	 *domainPtr;
    register Fsio_FileIOHandle *handlePtr;	/* I/O handle for the file. */
    Fscache_Block *blockPtr;	/* Cache block to write out. */
{
    register Ofs_Domain	*ofsPtr = OFS_PTR_FROM_DOMAIN(domainPtr);
    ReturnStatus		status;
    int				diskBlock;

    if (handlePtr->hdr.fileID.minor == 0 || blockPtr->diskBlock < 0) {
	/*
	 * The block number is a raw block number counting from the
	 * beginning of the domain.
	 * Descriptor blocks are indicated by a handle with a 0 file number 
	 * and indirect a negative block number (indirect blocks).
	 */
	if (blockPtr->diskBlock < 0) {
	    diskBlock = -blockPtr->diskBlock;
	} else {
	    diskBlock = blockPtr->diskBlock;
	}
	fs_Stats.gen.physBytesWritten += blockPtr->blockSize;
	status = OfsDeviceBlockIO(ofsPtr, FS_WRITE,
		     diskBlock, FS_FRAGMENTS_PER_BLOCK, blockPtr->blockAddr);
    } else {
	/*
	 * The block number is relative to the start of the data blocks.
	 */
	status = OfsVerifyBlockWrite(ofsPtr, blockPtr);
	if (status == SUCCESS) {
	    status = OfsDeviceBlockIO(ofsPtr, FS_WRITE,
		   blockPtr->diskBlock + 
		   ofsPtr->headerPtr->dataOffset * FS_FRAGMENTS_PER_BLOCK,
		   (blockPtr->blockSize - 1) / FS_FRAGMENT_SIZE + 1,
		   blockPtr->blockAddr);
        }
	if (status == SUCCESS) {
	    Fs_StatAdd(blockPtr->blockSize, fs_Stats.gen.fileBytesWritten,
		   fs_Stats.gen.fileWriteOverflow);
#ifdef SOSP91
	    if (proc_RunningProcesses[0] != (Proc_ControlBlock *) NIL) {
		if ((proc_RunningProcesses[0]->state == PROC_MIGRATED) ||
			(proc_RunningProcesses[0]->genFlags &
			(PROC_FOREIGN | PROC_MIGRATING))) {
		    Fs_StatAdd(blockPtr->blockSize,
			    fs_SospMigStats.gen.fileBytesWritten, 
			    fs_SospMigStats.gen.fileWriteOverflow);
		}
	    }
#endif SOSP91
	}
    }
    return(status);
}

/*
 *----------------------------------------------------------------------
 *
 * OfsDeviceBlockIO --
 *
 *	Map a file system block address to a block device block address 
 *	perform the requested operation.
 *
 * NOTE: This routine is temporary and should be replaced when the file system
 *	 is converted to use the async block io interface.
 *
 * Results:
 *	The return status of the operation.
 *
 * Side effects:
 *	Blocks may be written or read.
 *
 *----------------------------------------------------------------------
 */

ReturnStatus
OfsDeviceBlockIO(ofsPtr, readWriteFlag, fragNumber, numFrags, buffer)
    Ofs_Domain	*ofsPtr;	/* Domain */
    int readWriteFlag;		/* FS_READ or FS_WRITE */
    int fragNumber;		/* CAREFUL, fragment index, not block index.
				 * This is relative to start of device. */
    int numFrags;		/* CAREFUL, number of fragments, not blocks */
    Address buffer;		/* I/O buffer */
{
    ReturnStatus status = SUCCESS;	/* General return code */
    int firstSector;		/* Starting sector of transfer */
    DevBlockDeviceRequest	request;
    int				transferCount;

    if ((fragNumber % FS_FRAGMENTS_PER_BLOCK) != 0) {
	/*
	 * The I/O doesn't start on a block boundary.  Transfer the
	 * first few extra fragments to get things going on a block boundary.
	 */
	register int extraFrags;

	extraFrags = FS_FRAGMENTS_PER_BLOCK -
		    (fragNumber % FS_FRAGMENTS_PER_BLOCK);
	if (extraFrags > numFrags) {
	    extraFrags = numFrags;
	}
	firstSector = OfsBlocksToSectors(fragNumber, 
			&ofsPtr->headerPtr->geometry);
	request.operation = readWriteFlag;
	request.startAddress = firstSector * DEV_BYTES_PER_SECTOR;
	request.startAddrHigh = 0;
	request.bufferLen = extraFrags * FS_FRAGMENT_SIZE;
	request.buffer = buffer;
	status = Dev_BlockDeviceIOSync(ofsPtr->blockDevHandlePtr, &request, 
					&transferCount);
	extraFrags = transferCount / FS_FRAGMENT_SIZE;
	fragNumber += extraFrags;
	buffer += transferCount;
	numFrags -= extraFrags;
	if (status != SUCCESS) {
	    return(status);
	}
    }
    while (numFrags >= FS_FRAGMENTS_PER_BLOCK) {
	/*
	 * Transfer whole blocks.
	 */
	firstSector = OfsBlocksToSectors(fragNumber, 
			&ofsPtr->headerPtr->geometry);
	request.operation = readWriteFlag;
	request.startAddress = firstSector * DEV_BYTES_PER_SECTOR;
	request.startAddrHigh = 0;
	request.bufferLen = FS_BLOCK_SIZE;
	request.buffer = buffer;
	status = Dev_BlockDeviceIOSync(ofsPtr->blockDevHandlePtr, &request, 
					&transferCount);
	fragNumber += FS_FRAGMENTS_PER_BLOCK;
	buffer += FS_BLOCK_SIZE;
	numFrags -= FS_FRAGMENTS_PER_BLOCK;
	if (status != SUCCESS) {
	    return(status);
	}
    }
    if (numFrags > 0) {
	/*
	 * Transfer the left over fragments.
	 */
	firstSector = OfsBlocksToSectors(fragNumber, 
			&ofsPtr->headerPtr->geometry);
	request.operation = readWriteFlag;
	request.startAddress = firstSector * DEV_BYTES_PER_SECTOR;
	request.startAddrHigh = 0;
	request.bufferLen = numFrags * FS_FRAGMENT_SIZE;
	request.buffer = buffer;
	status = Dev_BlockDeviceIOSync(ofsPtr->blockDevHandlePtr, &request, 
					&transferCount);
    }
    return(status);
}


/*
 *----------------------------------------------------------------------
 *
 * FsioVerifyBlockWrite --
 *
 *	Double check this block to make sure it seems like were writing
 *	it to the right place.
 *
 * Results:
 *	Error code if an inconsistency was detected.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
ReturnStatus
OfsVerifyBlockWrite(ofsPtr, blockPtr)
    Ofs_Domain	*ofsPtr;
    Fscache_Block *blockPtr;		/* Block about to be written out */
{
    ReturnStatus status = SUCCESS;
    Fs_HandleHeader *hdrPtr = blockPtr->cacheInfoPtr->hdrPtr;
    OfsBlockIndexInfo		 indexInfo;

    if (blockPtr->fileNum != hdrPtr->fileID.minor) {
	printf("FsioVerifyBlockWrite: block being written to wrong file\n");
	printf("	Logical block %d block's file %d owning file %d \"%s\"\n",
	    blockPtr->blockNum, blockPtr->fileNum,
	    hdrPtr->fileID.minor, Fsutil_HandleName(hdrPtr));
	return(FS_INVALID_ARG);
    }
    status = OfsGetFirstIndex(ofsPtr, (Fsio_FileIOHandle *)hdrPtr, 
			blockPtr->blockNum, &indexInfo,
			(int)FSCACHE_DONT_BLOCK);
    if (status == FS_WOULD_BLOCK) {
	/*
	 * No room in the cache for the index blocks needed to check.
	 * assume the write is ok.
	 */
	return(SUCCESS);
    } else if (status != SUCCESS) {
	return(status);
    }
    if (indexInfo.blockAddrPtr == (int *)NIL ||
	*indexInfo.blockAddrPtr == FSDM_NIL_INDEX) {
	printf("FsioVerifyBlockWrite: no block index\n");
	panic("	Logical block %d owning file %d \"%s\"\n",
	    blockPtr->blockNum, hdrPtr->fileID.minor, Fsutil_HandleName(hdrPtr));
	status = FS_INVALID_ARG;
	goto exit;
    }
    if (*indexInfo.blockAddrPtr != blockPtr->diskBlock) {
	printf("OfsVerifyBlockWrite: disk block mismatch\n");
	panic("	Logical block %d old disk block %d new %d owning file %d \"%s\"\n",
	    blockPtr->blockNum, *indexInfo.blockAddrPtr,
	    blockPtr->diskBlock,
	    hdrPtr->fileID.minor, Fsutil_HandleName(hdrPtr));
	status = FS_INVALID_ARG;
	goto exit;
    }

exit:
    OfsEndIndex((Fsio_FileIOHandle *)hdrPtr, &indexInfo, FALSE);
    return(status);
}

/*
 * ----------------------------------------------------------------------------
 *
 * BlockMatch --
 *
 * 	Cache backend block type match.  Ofs doesn't care about the 
 *	order of blocks returned by GetDirtyBlocks.
 *
 * Results:
 *	TRUE.
 *
 * Side effects:
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
Boolean
BlockMatch(blockPtr, clientData)
    Fscache_Block *blockPtr;
    ClientData	   clientData;
{
    return TRUE;
}


/*
 * ----------------------------------------------------------------------------
 *
 * FileMatch --
 *
 * 	Cache backend file match. Ofs will take any file the cache wants
 *	it to write.
 *
 * Results:
 *	TRUE.
 *
 * Side effects:
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
static Boolean
FileMatch(cacheInfoPtr, clientData)
    Fscache_FileInfo *cacheInfoPtr;
    ClientData	clientData;
{
    return TRUE;
}


/*
 * ----------------------------------------------------------------------------
 *
 * Ofs_StartWriteBack --
 *
 * 	Start a block cleaner process for the specified domain.
 *
 * Results:
 *	TRUE if a block cleaner was started.
 *
 * Side effects:
 *	Number of block cleaner processes may be incremented.
 *
 * ----------------------------------------------------------------------------
 */
Boolean
Ofs_StartWriteBack(backendPtr)
    Fscache_Backend *backendPtr;	/* Backend to start writeback. */
{
    LOCK_MONITOR;
    if (ofsBlockCleaners < fscache_MaxBlockCleaners) {
	Proc_CallFunc(Ofs_CleanBlocks, (ClientData) backendPtr, 0);
	ofsBlockCleaners++;
	UNLOCK_MONITOR;
	return TRUE;
    }
    UNLOCK_MONITOR;
    return FALSE;
}


/*
 * ----------------------------------------------------------------------------
 *
 *	Functions to clean dirty blocks.
 *
 * ----------------------------------------------------------------------------
 */


/*
 * ----------------------------------------------------------------------------
 *
 * Ofs_CleanBlocks
 *
 *	Write all blocks on the dirty list to disk.  Called either from
 *	a block cleaner process or synchronously during system shutdown.
 *
 * Results:
 *     	None.
 *
 * Side effects:
 *     	The dirty list is emptied.
 *
 * ----------------------------------------------------------------------------
 */
/*ARGSUSED*/
void
Ofs_CleanBlocks(data, callInfoPtr)
    ClientData		data;		/* Background flag.  If TRUE it means
					 * we are called from a block cleaner
					 * process.  Otherwise we being called
					 * synchrounously during a shutdown */
    Proc_CallInfo	*callInfoPtr;	/* Not Used. */
{
    Fscache_Block	*blockPtr;
    ReturnStatus		status;
    int				lastDirtyBlock;
    Fscache_FileInfo		*cacheInfoPtr;
    Fscache_Backend		*backendPtr;
    int				numWrites;

    backendPtr = (Fscache_Backend *) data;
    cacheInfoPtr = Fscache_GetDirtyFile(backendPtr, TRUE, FileMatch, 
					(ClientData) NIL);
    while (cacheInfoPtr != (Fscache_FileInfo *)NIL) {
	blockPtr = Fscache_GetDirtyBlock(cacheInfoPtr, BlockMatch, 
					(ClientData) 0, &lastDirtyBlock);
	numWrites = 0;
	while (blockPtr != (Fscache_Block *) NIL) {
	    /*
	     * Write the block.
	     */
#ifdef SOSP91
	    Fscache_AddBlockToStats(cacheInfoPtr, blockPtr);
#endif SOSP91
	    status = Fsdm_FileBlockWrite
		    (cacheInfoPtr->hdrPtr, blockPtr, lastDirtyBlock);
	    numWrites++;
	    Fscache_ReturnDirtyBlock(blockPtr, status);
	    if (numWrites < ofsBlockWritesPerFile) {
		blockPtr = Fscache_GetDirtyBlock(cacheInfoPtr, BlockMatch, 
					(ClientData) 0, &lastDirtyBlock);
	    } else {
		break;
	    }
	}
#ifdef SOSP91
	cacheInfoPtr->flags &= ~FSCACHE_REASON_FLAGS;
#endif SOSP91
	Fscache_ReturnDirtyFile(cacheInfoPtr, FALSE);
	cacheInfoPtr = Fscache_GetDirtyFile(backendPtr, TRUE, FileMatch, 
					(ClientData) NIL);
    }
    FscacheBackendIdle(backendPtr);
    LOCK_MONITOR;
    ofsBlockCleaners--;
    UNLOCK_MONITOR;
}



