;/* ASyncIO.c - Execute me to compile with SAS/C 6.56 sc data=near nominc strmer streq nostkchk saveds ign=73 AsyncIO.c quit */ /* (c) Copyright 1993-1999 Amiga, Inc. All rights reserved. */ /* The information contained herein is subject to change without */ /* notice, and is provided "as is" without warranty of any kind, */ /* either expressed or implied. The entire risk as to the use of */ /* this information is assumed by the user. */ #include <exec/types.h> #include <exec/memory.h> #include <dos/dos.h> #include <dos/dosextens.h> #include <clib/exec_protos.h> #include <clib/dos_protos.h> #include <pragmas/exec_pragmas.h> #include <pragmas/dos_pragmas.h> #include "asyncio.h" /*****************************************************************************/ extern struct Library *DOSBase; extern struct Library *SysBase; /*****************************************************************************/ /* this macro lets us long-align structures on the stack */ #define D_S(type,name) char a_##name[sizeof(type)+3]; \ type *name = (type *)((LONG)(a_##name+3) & ~3); /*****************************************************************************/ /* send out an async packet to the file system. */ static VOID SendPacket(struct AsyncFile *file, APTR arg2) { file->af_Packet.sp_Pkt.dp_Port = &file->af_PacketPort; file->af_Packet.sp_Pkt.dp_Arg2 = (LONG)arg2; PutMsg(file->af_Handler, &file->af_Packet.sp_Msg); file->af_PacketPending = TRUE; } /*****************************************************************************/ /* this function waits for a packet to come back from the file system. If no * packet is pending, state from the previous packet is returned. This ensures * that once an error occurs, it state is maintained for the rest of the life * of the file handle. * * This function also deals with IO errors, bringing up the needed DOS * requesters to let the user retry an operation or cancel it. */ static LONG WaitPacket(struct AsyncFile *file) { LONG bytes; if (file->af_PacketPending) { /* mark packet as no longer pending since we are going to get it */ file->af_PacketPending = FALSE; while (TRUE) { /* This enables signalling when a packet comes back to the port */ file->af_PacketPort.mp_Flags = PA_SIGNAL; /* Wait for the packet to come back, and remove it from the message * list. Since we know no other packets can come in to the port, we can * safely use Remove() instead of GetMsg(). If other packets could come in, * we would have to use GetMsg(), which correctly arbitrates access in such * a case */ Remove((struct Node *)WaitPort(&file->af_PacketPort)); /* set the port type back to PA_IGNORE so we won't be bothered with * spurious signals */ file->af_PacketPort.mp_Flags = PA_IGNORE; bytes = file->af_Packet.sp_Pkt.dp_Res1; if (bytes >= 0) { /* packet didn't report an error, so bye... */ return(bytes); } /* see if the user wants to try again... */ if (ErrorReport(file->af_Packet.sp_Pkt.dp_Res2, REPORT_STREAM, file->af_File,NULL)) return(-1); /* user wants to try again, resend the packet */ SendPacket(file,file->af_Buffers[file->af_CurrentBuf]); } } /* last packet's error code, or 0 if packet was never sent */ SetIoErr(file->af_Packet.sp_Pkt.dp_Res2); return(file->af_Packet.sp_Pkt.dp_Res1); } /*****************************************************************************/ /* this function puts the packet back on the message list of our * message port. */ static VOID RequeuePacket(struct AsyncFile *file) { AddHead(&file->af_PacketPort.mp_MsgList,&file->af_Packet.sp_Msg.mn_Node); file->af_PacketPending = TRUE; } /*****************************************************************************/ /* this function records a failure from a synchronous DOS call into the * packet so that it gets picked up by the other IO routines in this module */ VOID RecordSyncFailure(struct AsyncFile *file) { file->af_Packet.sp_Pkt.dp_Res1 = -1; file->af_Packet.sp_Pkt.dp_Res2 = IoErr(); } /*****************************************************************************/ struct AsyncFile *OpenAsync(const STRPTR fileName, UBYTE accessMode, LONG bufferSize) { struct AsyncFile *file; struct FileHandle *fh; BPTR handle; BPTR lock; LONG blockSize; D_S(struct InfoData,infoData); handle = NULL; file = NULL; lock = NULL; if (accessMode == MODE_READ) { if (handle = Open(fileName,MODE_OLDFILE)) lock = DupLockFromFH(handle); } else { if (accessMode == MODE_WRITE) { handle = Open(fileName,MODE_NEWFILE); } else if (accessMode == MODE_APPEND) { /* in append mode, we open for writing, and then seek to the * end of the file. That way, the initial write will happen at * the end of the file, thus extending it */ if (handle = Open(fileName,MODE_READWRITE)) { if (Seek(handle,0,OFFSET_END) < 0) { Close(handle); handle = NULL; } } } /* we want a lock on the same device as where the file is. We can't * use DupLockFromFH() for a write-mode file though. So we get sneaky * and get a lock on the parent of the file */ if (handle) lock = ParentOfFH(handle); } if (handle) { /* if it was possible to obtain a lock on the same device as the * file we're working on, get the block size of that device and * round up our buffer size to be a multiple of the block size. * This maximizes DMA efficiency. */ blockSize = 512; if (lock) { if (Info(lock,infoData)) { blockSize = infoData->id_BytesPerBlock; bufferSize = (((bufferSize + blockSize - 1) / blockSize) * blockSize) * 2; } UnLock(lock); } /* now allocate the ASyncFile structure, as well as the read buffers. * Add 15 bytes to the total size in order to allow for later * quad-longword alignement of the buffers */ if (file = AllocVec(sizeof(struct AsyncFile) + bufferSize + 15,MEMF_ANY)) { file->af_File = handle; file->af_ReadMode = (accessMode == MODE_READ); file->af_BlockSize = blockSize; /* initialize the ASyncFile structure. We do as much as we can here, * in order to avoid doing it in more critical sections * * Note how the two buffers used are quad-longword aligned. This * helps performance on 68040 systems with copyback cache. Aligning * the data avoids a nasty side-effect of the 040 caches on DMA. * Not aligning the data causes the device driver to have to do * some magic to avoid the cache problem. This magic will generally * involve flushing the CPU caches. This is very costly on an 040. * Aligning things avoids the need for magic, at the cost of at * most 15 bytes of ram. */ fh = BADDR(file->af_File); file->af_Handler = fh->fh_Type; file->af_BufferSize = bufferSize / 2; file->af_Buffers[0] = (APTR)(((ULONG)file + sizeof(struct AsyncFile) + 15) & 0xfffffff0); file->af_Buffers[1] = (APTR)((ULONG)file->af_Buffers[0] + file->af_BufferSize); file->af_Offset = file->af_Buffers[0]; file->af_CurrentBuf = 0; file->af_SeekOffset = 0; file->af_PacketPending = FALSE; /* this is the port used to get the packets we send out back. * It is initialized to PA_IGNORE, which means that no signal is * generated when a message comes in to the port. The signal bit * number is initialized to SIGB_SINGLE, which is the special bit * that can be used for one-shot signalling. The signal will never * be set, since the port is of type PA_IGNORE. We'll change the * type of the port later on to PA_SIGNAL whenever we need to wait * for a message to come in. * * The trick used here avoids the need to allocate an extra signal * bit for the port. It is quite efficient. */ file->af_PacketPort.mp_MsgList.lh_Head = (struct Node *)&file->af_PacketPort.mp_MsgList.lh_Tail; file->af_PacketPort.mp_MsgList.lh_Tail = NULL; file->af_PacketPort.mp_MsgList.lh_TailPred = (struct Node *)&file->af_PacketPort.mp_MsgList.lh_Head; file->af_PacketPort.mp_Node.ln_Type = NT_MSGPORT; file->af_PacketPort.mp_Flags = PA_IGNORE; file->af_PacketPort.mp_SigBit = SIGB_SINGLE; file->af_PacketPort.mp_SigTask = FindTask(NULL); file->af_Packet.sp_Pkt.dp_Link = &file->af_Packet.sp_Msg; file->af_Packet.sp_Pkt.dp_Arg1 = fh->fh_Arg1; file->af_Packet.sp_Pkt.dp_Arg3 = file->af_BufferSize; file->af_Packet.sp_Pkt.dp_Res1 = 0; file->af_Packet.sp_Pkt.dp_Res2 = 0; file->af_Packet.sp_Msg.mn_Node.ln_Name = (STRPTR)&file->af_Packet.sp_Pkt; file->af_Packet.sp_Msg.mn_Node.ln_Type = NT_MESSAGE; file->af_Packet.sp_Msg.mn_Length = sizeof(struct StandardPacket); if (accessMode == MODE_READ) { /* if we are in read mode, send out the first read packet to * the file system. While the application is getting ready to * read data, the file system will happily fill in this buffer * with DMA transfers, so that by the time the application * needs the data, it will be in the buffer waiting */ file->af_Packet.sp_Pkt.dp_Type = ACTION_READ; file->af_BytesLeft = 0; if (file->af_Handler) SendPacket(file,file->af_Buffers[0]); } else { file->af_Packet.sp_Pkt.dp_Type = ACTION_WRITE; file->af_BytesLeft = file->af_BufferSize; } } else { Close(handle); } } return(file); } /*****************************************************************************/ LONG CloseAsync(struct AsyncFile *file) { LONG result; if (file) { result = WaitPacket(file); if (result >= 0) { if (!file->af_ReadMode) { /* this will flush out any pending data in the write buffer */ result = Write(file->af_File, file->af_Buffers[file->af_CurrentBuf], file->af_BufferSize - file->af_BytesLeft); } } Close(file->af_File); FreeVec(file); } else { SetIoErr(ERROR_INVALID_LOCK); result = -1; } return(result); } /*****************************************************************************/ LONG ReadAsync(struct AsyncFile *file, APTR buffer, LONG numBytes) { LONG totalBytes; LONG bytesArrived; totalBytes = 0; /* if we need more bytes than there are in the current buffer, enter the * read loop */ while (numBytes > file->af_BytesLeft) { /* drain buffer */ CopyMem(file->af_Offset,buffer,file->af_BytesLeft); numBytes -= file->af_BytesLeft; buffer = (APTR)((ULONG)buffer + file->af_BytesLeft); totalBytes += file->af_BytesLeft; file->af_BytesLeft = 0; bytesArrived = WaitPacket(file); if (bytesArrived <= 0) { if (bytesArrived == 0) return(totalBytes); return(-1); } /* ask that the buffer be filled */ SendPacket(file,file->af_Buffers[1-file->af_CurrentBuf]); if (file->af_SeekOffset > bytesArrived) file->af_SeekOffset = bytesArrived; file->af_Offset = (APTR)((ULONG)file->af_Buffers[file->af_CurrentBuf] + file->af_SeekOffset); file->af_CurrentBuf = 1 - file->af_CurrentBuf; file->af_BytesLeft = bytesArrived - file->af_SeekOffset; file->af_SeekOffset = 0; } CopyMem(file->af_Offset,buffer,numBytes); file->af_BytesLeft -= numBytes; file->af_Offset = (APTR)((ULONG)file->af_Offset + numBytes); return (totalBytes + numBytes); } /*****************************************************************************/ LONG ReadCharAsync(struct AsyncFile *file) { unsigned char ch; if (file->af_BytesLeft) { /* if there is at least a byte left in the current buffer, get it * directly. Also update all counters */ ch = *(char *)file->af_Offset; file->af_BytesLeft--; file->af_Offset = (APTR)((ULONG)file->af_Offset + 1); return((LONG)ch); } /* there were no characters in the current buffer, so call the main read * routine. This has the effect of sending a request to the file system to * have the current buffer refilled. After that request is done, the * character is extracted for the alternate buffer, which at that point * becomes the "current" buffer */ if (ReadAsync(file,&ch,1) > 0) return((LONG)ch); /* We couldn't read above, so fail */ return(-1); } /*****************************************************************************/ LONG WriteAsync(struct AsyncFile *file, APTR buffer, LONG numBytes) { LONG totalBytes; totalBytes = 0; while (numBytes > file->af_BytesLeft) { /* this takes care of NIL: */ if (!file->af_Handler) { file->af_Offset = file->af_Buffers[0]; file->af_BytesLeft = file->af_BufferSize; return(numBytes); } if (file->af_BytesLeft) { CopyMem(buffer,file->af_Offset,file->af_BytesLeft); numBytes -= file->af_BytesLeft; buffer = (APTR)((ULONG)buffer + file->af_BytesLeft); totalBytes += file->af_BytesLeft; } if (WaitPacket(file) < 0) return(-1); /* send the current buffer out to disk */ SendPacket(file,file->af_Buffers[file->af_CurrentBuf]); file->af_CurrentBuf = 1 - file->af_CurrentBuf; file->af_Offset = file->af_Buffers[file->af_CurrentBuf]; file->af_BytesLeft = file->af_BufferSize; } CopyMem(buffer,file->af_Offset,numBytes); file->af_BytesLeft -= numBytes; file->af_Offset = (APTR)((ULONG)file->af_Offset + numBytes); return (totalBytes + numBytes); } /*****************************************************************************/ LONG WriteCharAsync(struct AsyncFile *file, UBYTE ch) { if (file->af_BytesLeft) { /* if there's any room left in the current buffer, directly write * the byte into it, updating counters and stuff. */ *(UBYTE *)file->af_Offset = ch; file->af_BytesLeft--; file->af_Offset = (APTR)((ULONG)file->af_Offset + 1); /* one byte written */ return(1); } /* there was no room in the current buffer, so call the main write * routine. This will effectively send the current buffer out to disk, * wait for the other buffer to come back, and then put the byte into * it. */ return(WriteAsync(file,&ch,1)); } /*****************************************************************************/ LONG SeekAsync(struct AsyncFile *file, LONG position, BYTE mode) { LONG current, target; LONG minBuf, maxBuf; LONG bytesArrived; LONG diff; LONG filePos; LONG roundTarget; D_S(struct FileInfoBlock,fib); bytesArrived = WaitPacket(file); if (bytesArrived < 0) return(-1); if (file->af_ReadMode) { /* figure out what the actual file position is */ filePos = Seek(file->af_File,OFFSET_CURRENT,0); if (filePos < 0) { RecordSyncFailure(file); return(-1); } /* figure out what the caller's file position is */ current = filePos - (file->af_BytesLeft+bytesArrived); /* figure out the absolute offset within the file where we must seek to */ if (mode == MODE_CURRENT) { target = current + position; } else if (mode == MODE_START) { target = position; } else /* if (mode == MODE_END) */ { if (!ExamineFH(file->af_File,fib)) { RecordSyncFailure(file); return(-1); } target = fib->fib_Size + position; } /* figure out what range of the file is currently in our buffers */ minBuf = current - (LONG)((ULONG)file->af_Offset - (ULONG)file->af_Buffers[1 - file->af_CurrentBuf]); maxBuf = current + file->af_BytesLeft + bytesArrived; /* WARNING: this is one too big */ diff = target - current; if ((target < minBuf) || (target >= maxBuf)) { /* the target seek location isn't currently in our buffers, so * move the actual file pointer to the desired location, and then * restart the async read thing... */ /* this is to keep our file reading block-aligned on the device. * block-aligned reads are generally quite a bit faster, so it is * worth the trouble to keep things aligned */ roundTarget = (target / file->af_BlockSize) * file->af_BlockSize; if (Seek(file->af_File,roundTarget-filePos,OFFSET_CURRENT) < 0) { RecordSyncFailure(file); return(-1); } SendPacket(file,file->af_Buffers[0]); file->af_SeekOffset = target-roundTarget; file->af_BytesLeft = 0; file->af_CurrentBuf = 0; } else if ((target < current) || (diff <= file->af_BytesLeft)) { /* one of the two following things is true: * * 1. The target seek location is within the current read buffer, * but before the current location within the buffer. Move back * within the buffer and pretend we never got the pending packet, * just to make life easier, and faster, in the read routine. * * 2. The target seek location is ahead within the current * read buffer. Advance to that location. As above, pretend to * have never received the pending packet. */ RequeuePacket(file); file->af_BytesLeft -= diff; file->af_Offset = (APTR)((ULONG)file->af_Offset + diff); } else { /* at this point, we know the target seek location is within * the buffer filled in by the packet that we just received * at the start of this function. Throw away all the bytes in the * current buffer, send a packet out to get the async thing going * again, readjust buffer pointers to the seek location, and return * with a grin on your face... :-) */ diff -= file->af_BytesLeft; SendPacket(file,file->af_Buffers[1-file->af_CurrentBuf]); file->af_Offset = (APTR)((ULONG)file->af_Buffers[file->af_CurrentBuf] + diff); file->af_CurrentBuf = 1 - file->af_CurrentBuf; file->af_BytesLeft = bytesArrived - diff; } } else { if (Write(file->af_File, file->af_Buffers[file->af_CurrentBuf], file->af_BufferSize - file->af_BytesLeft) < 0) { RecordSyncFailure(file); return(-1); } /* this will unfortunately generally result in non block-aligned file * access. We could be sneaky and try to resync our file pos at a * later time, but we won't bother. Seeking in write-only files is * relatively rare (except when writing IFF files with unknown chunk * sizes, where the chunk size has to be written after the chunk data) */ current = Seek(file->af_File,position,mode); if (current < 0) { RecordSyncFailure(file); return(-1); } file->af_BytesLeft = file->af_BufferSize; file->af_CurrentBuf = 0; } return(current); }