mirror of
				https://github.com/sheumann/AFPBridge.git
				synced 2025-10-24 11:20:49 -07:00 
			
		
		
		
	It is now possible to have AFP volumes mounted over both AppleTalk and TCP/IP simultaneously.
		
			
				
	
	
		
			351 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			351 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #pragma noroot
 | |
| 
 | |
| #include <AppleTalk.h>
 | |
| #include <string.h>
 | |
| 
 | |
| #include "session.h"
 | |
| #include "aspinterface.h"
 | |
| #include "dsi.h"
 | |
| #include "tcpconnection.h"
 | |
| #include "endian.h"
 | |
| #include "readtcp.h"
 | |
| #include "asmglue.h"
 | |
| #include "cmdproc.h"
 | |
| #include "installcmds.h"
 | |
| #include "atipmapping.h"
 | |
| 
 | |
| typedef struct FPReadRec {
 | |
|     Word CommandCode;   /* includes pad byte */
 | |
|     Word OForkRefNum;
 | |
|     LongWord Offset;
 | |
|     LongWord ReqCount;
 | |
|     Byte NewLineMask;
 | |
|     Byte NewLineChar;
 | |
| } FPReadRec;
 | |
| 
 | |
| #define kFPRead 27
 | |
| 
 | |
| static void EndSession(Session *sess, Boolean callAttnRoutine);
 | |
| 
 | |
| static void DoSPGetStatus(Session *sess, ASPGetStatusRec *commandRec);
 | |
| static void DoSPOpenSession(Session *sess, ASPOpenSessionRec *commandRec);
 | |
| static void DoSPCloseSession(Session *sess, ASPCloseSessionRec *commandRec);
 | |
| static void DoSPCommand(Session *sess, ASPCommandRec *commandRec);
 | |
| static void DoSPWrite(Session *sess, ASPWriteRec *commandRec);
 | |
| 
 | |
| 
 | |
| Session sessionTbl[MAX_SESSIONS];
 | |
| 
 | |
| #pragma databank 1
 | |
| LongWord DispatchASPCommand(SPCommandRec *commandRec) {
 | |
|     Session *sess;
 | |
|     unsigned int i;
 | |
|     Word stateReg;
 | |
|     
 | |
|     stateReg = ForceRomIn();
 | |
| 
 | |
|     if (commandRec->command == aspGetStatusCommand 
 | |
|         || commandRec->command==aspOpenSessionCommand)
 | |
|     {
 | |
|         if (((ASPGetStatusRec*)commandRec)->slsNet != atipMapping.networkNumber
 | |
|             || ((ASPGetStatusRec*)commandRec)->slsNode != atipMapping.node
 | |
|             || ((ASPGetStatusRec*)commandRec)->slsSocket != atipMapping.socket)
 | |
|         {
 | |
|             goto callOrig;
 | |
|         }
 | |
| 
 | |
|         for (i = 0; i < MAX_SESSIONS; i++) {
 | |
|             if (sessionTbl[i].dsiStatus == unused)
 | |
|                 break;
 | |
|         }
 | |
|         if (i == MAX_SESSIONS) {
 | |
|             CompleteASPCommand(sess, aspTooManySessions);
 | |
|             goto ret;
 | |
|         }
 | |
|         sess = &sessionTbl[i];
 | |
|         sess->spCommandRec = commandRec;
 | |
|         
 | |
|         if (!StartTCPConnection(sess)) {
 | |
|             FlagFatalError(sess, commandRec->result);
 | |
|             goto ret;
 | |
|         }
 | |
|         sess->dsiStatus = awaitingHeader;
 | |
|         InitReadTCP(sess, DSI_HEADER_SIZE, &sess->reply);
 | |
|     } else {
 | |
|         if (commandRec->refNum < SESSION_NUM_START) {
 | |
|             goto callOrig;
 | |
|         }
 | |
|         
 | |
|         /*
 | |
|          * Hack to avoid hangs/crashes related to the AppleShare FST's
 | |
|          * asynchronous polling to check if volumes have been modified.
 | |
|          * This effectively disables that mechanism (which is the only
 | |
|          * real user of asynchronous ASP calls).
 | |
|          * 
 | |
|          * TODO Identify and fix the underlying issue.
 | |
|          */
 | |
|         if ((commandRec->async & AT_ASYNC)
 | |
|             && commandRec->command == aspCommandCommand)
 | |
|         {
 | |
|             commandRec->result = atSyncErr;
 | |
|             CallCompletionRoutine((void*)commandRec->completionPtr);
 | |
|             goto ret;
 | |
|         }
 | |
|         
 | |
|         i = commandRec->refNum - SESSION_NUM_START;
 | |
|         sess = &sessionTbl[i];
 | |
|         sess->spCommandRec = commandRec;
 | |
|     }
 | |
| 
 | |
|     // TODO properly handle all cases of getting a command while
 | |
|     // one is in progress
 | |
|     if (commandRec->command != aspCloseSessionCommand) {
 | |
|         if (sess->commandPending) {
 | |
|             CompleteASPCommand(sess, aspSessNumErr);
 | |
|             goto ret;
 | |
|         }
 | |
|         sess->commandPending = TRUE;
 | |
|     }
 | |
|     
 | |
|     switch (commandRec->command) {
 | |
|     case aspGetStatusCommand:
 | |
|         DoSPGetStatus(sess, (ASPGetStatusRec *)commandRec);
 | |
|         break;
 | |
|     case aspOpenSessionCommand:
 | |
|         DoSPOpenSession(sess, (ASPOpenSessionRec *)commandRec);
 | |
|         break;
 | |
|     case aspCloseSessionCommand:
 | |
|         DoSPCloseSession(sess, (ASPCloseSessionRec *)commandRec);
 | |
|         break;
 | |
|     case aspCommandCommand:
 | |
|         DoSPCommand(sess, (ASPCommandRec *)commandRec);
 | |
|         break;
 | |
|     case aspWriteCommand:
 | |
|         DoSPWrite(sess, (ASPWriteRec *)commandRec);
 | |
|         break;
 | |
|     }
 | |
|     
 | |
|     if ((commandRec->async & AT_ASYNC) && sess->commandPending) {
 | |
|         commandRec->result = aspBusyErr;  // indicate call in process
 | |
|         goto ret;
 | |
|     }
 | |
|     
 | |
|     // if we're here, the call is synchronous -- we must complete it
 | |
|     
 | |
|     if (commandRec->command == aspCloseSessionCommand) {
 | |
|         FinishASPCommand(sess);
 | |
|     } else {
 | |
|         while (sess->commandPending) {
 | |
|             PollForData(sess);
 | |
|         }
 | |
|     }
 | |
| 
 | |
| ret:    
 | |
|     RestoreStateReg(stateReg);
 | |
|     return 0;
 | |
| 
 | |
| callOrig:
 | |
|     RestoreStateReg(stateReg);
 | |
|     return oldCmds[commandRec->command];
 | |
| }
 | |
| #pragma databank 0
 | |
| 
 | |
| static void DoSPGetStatus(Session *sess, ASPGetStatusRec *commandRec) {
 | |
|     static const Word kFPGetSrvrInfo = 15;
 | |
|     sess->request.flags = DSI_REQUEST;
 | |
|     sess->request.command = DSIGetStatus;
 | |
|     sess->request.requestID = htons(sess->nextRequestID++);
 | |
|     sess->request.writeOffset = 0;
 | |
|     sess->request.totalDataLength = htonl(2);
 | |
|     sess->replyBuf = (void*)commandRec->bufferAddr;
 | |
|     sess->replyBufLen = commandRec->bufferLength;
 | |
|     
 | |
|     SendDSIMessage(sess, &sess->request, &kFPGetSrvrInfo, NULL);
 | |
| }
 | |
| 
 | |
| static void DoSPOpenSession(Session *sess, ASPOpenSessionRec *commandRec) {
 | |
|     sess->request.flags = DSI_REQUEST;
 | |
|     sess->request.command = DSIOpenSession;
 | |
|     sess->request.requestID = htons(sess->nextRequestID++);
 | |
|     sess->request.writeOffset = 0;
 | |
|     sess->request.totalDataLength = 0;
 | |
|     sess->replyBuf = NULL;
 | |
|     sess->replyBufLen = 0;
 | |
|     
 | |
|     SendDSIMessage(sess, &sess->request, NULL, NULL);
 | |
| }
 | |
| 
 | |
| static void DoSPCloseSession(Session *sess, ASPCloseSessionRec *commandRec) {
 | |
|     sess->request.flags = DSI_REQUEST;
 | |
|     sess->request.command = DSICloseSession;
 | |
|     sess->request.requestID = htons(sess->nextRequestID++);
 | |
|     sess->request.writeOffset = 0;
 | |
|     sess->request.totalDataLength = 0;
 | |
|     sess->replyBuf = NULL;
 | |
|     sess->replyBufLen = 0;
 | |
|     
 | |
|     SendDSIMessage(sess, &sess->request, NULL, NULL);
 | |
| }
 | |
| 
 | |
| static void DoSPCommand(Session *sess, ASPCommandRec *commandRec) {
 | |
|     sess->request.flags = DSI_REQUEST;
 | |
|     sess->request.command = DSICommand;
 | |
|     sess->request.requestID = htons(sess->nextRequestID++);
 | |
|     sess->request.writeOffset = 0;
 | |
|     sess->request.totalDataLength = htonl(commandRec->cmdBlkLength);
 | |
|     sess->replyBuf = (void*)(commandRec->replyBufferAddr & 0x00FFFFFF);
 | |
|     sess->replyBufLen = commandRec->replyBufferLen;
 | |
|     
 | |
|     /*
 | |
|      * If the client is requesting to read more data than will fit in its
 | |
|      * buffer, change the request to ask for only the amount that will fit.
 | |
|      * 
 | |
|      * This is necessary because ASP request and reply bodies are limited
 | |
|      * to a "quantum size" of 4624 bytes. Even if more data than that is
 | |
|      * requested, only that amount will be returned in a single reply.
 | |
|      * If the full read count requested is larger, it simply serves as a
 | |
|      * hint about the total amount that will be read across a series of
 | |
|      * FPRead requests.  The AppleShare FST relies on this behavior and
 | |
|      * only specifies a buffer size of 4624 bytes for such requests.
 | |
|      *
 | |
|      * DSI does not have this "quantum size" limitation, so the full
 | |
|      * requested amount would be returned but then not fit in the buffer.
 | |
|      * To avoid this, we modify the request to ask only for the amount of
 | |
|      * data that will fit in the buffer.  This removes the function of
 | |
|      * hinting about the total read size, but should work OK in practice.
 | |
|      *
 | |
|      * A similar issue could arise with FPEnumerate requests, but it
 | |
|      * actually doesn't in the case of the requests generated by the
 | |
|      * AppleShare FST, so we don't try to address them for now.
 | |
|      */
 | |
|     if (commandRec->cmdBlkLength == sizeof(FPReadRec)
 | |
|         && ((FPReadRec*)commandRec->cmdBlkAddr)->CommandCode == kFPRead
 | |
|         && ntohl(((FPReadRec*)commandRec->cmdBlkAddr)->ReqCount) 
 | |
|             > commandRec->replyBufferLen)
 | |
|     {
 | |
|         ((FPReadRec*)commandRec->cmdBlkAddr)->ReqCount = 
 | |
|             htonl(commandRec->replyBufferLen);
 | |
|     }
 | |
|     
 | |
|     /* Mask off high byte of addresses because PFI (at least) may
 | |
|      * put junk in them, and this can cause Marinetti errors. */
 | |
|     SendDSIMessage(sess, &sess->request,
 | |
|                    (void*)(commandRec->cmdBlkAddr & 0x00FFFFFF), NULL);
 | |
| }
 | |
| 
 | |
| static void DoSPWrite(Session *sess, ASPWriteRec *commandRec) {
 | |
|     sess->request.flags = DSI_REQUEST;
 | |
|     sess->request.command = DSIWrite;
 | |
|     sess->request.requestID = htons(sess->nextRequestID++);
 | |
|     sess->request.writeOffset = htonl(commandRec->cmdBlkLength);
 | |
|     sess->request.totalDataLength =
 | |
|         htonl(commandRec->cmdBlkLength + commandRec->writeDataLength);
 | |
|     sess->replyBuf = (void*)(commandRec->replyBufferAddr & 0x00FFFFFF);
 | |
|     sess->replyBufLen = commandRec->replyBufferLen;
 | |
|     
 | |
|     SendDSIMessage(sess, &sess->request,
 | |
|                    (void*)(commandRec->cmdBlkAddr & 0x00FFFFFF),
 | |
|                    (void*)(commandRec->writeDataAddr & 0x00FFFFFF));
 | |
| }
 | |
| 
 | |
| 
 | |
| void FlagFatalError(Session *sess, Word errorCode) {
 | |
|     sess->dsiStatus = error;
 | |
|     if (errorCode == 0) {
 | |
|         // TODO deduce better error code from Marinetti errors?
 | |
|         errorCode = aspNetworkErr;
 | |
|     }
 | |
| 
 | |
|     if (sess->commandPending) {
 | |
|         CompleteASPCommand(sess, errorCode);
 | |
|     }
 | |
|     
 | |
|     EndSession(sess, TRUE);
 | |
| }
 | |
| 
 | |
| 
 | |
| // Fill in any necessary data in the ASP command rec for a successful return
 | |
| void FinishASPCommand(Session *sess) {
 | |
|     LongWord dataLength;
 | |
|     
 | |
|     dataLength = ntohl(sess->reply.totalDataLength);
 | |
|     if (dataLength > 0xFFFF) {
 | |
|         // The IIgs ASP interfaces can't represent lengths over 64k.
 | |
|         // This should be detected as an error earlier, but let's make sure.
 | |
|         sess->spCommandRec->result = aspSizeErr;
 | |
|         goto complete;
 | |
|     }
 | |
| 
 | |
|     switch(sess->spCommandRec->command) {
 | |
|     case aspGetStatusCommand:
 | |
|         ((ASPGetStatusRec*)(sess->spCommandRec))->dataLength = dataLength;
 | |
|         break;
 | |
|     case aspOpenSessionCommand:
 | |
|         ((ASPOpenSessionRec*)(sess->spCommandRec))->refNum = 
 | |
|             (sess - &sessionTbl[0]) + SESSION_NUM_START;
 | |
|         break;
 | |
|     case aspCloseSessionCommand:
 | |
|         break;
 | |
|     case aspCommandCommand:
 | |
|         ((ASPCommandRec*)(sess->spCommandRec))->cmdResult =
 | |
|             sess->reply.errorCode;
 | |
|         ((ASPCommandRec*)(sess->spCommandRec))->replyLength = dataLength;
 | |
|         break;
 | |
|     case aspWriteCommand:
 | |
|         ((ASPWriteRec*)(sess->spCommandRec))->cmdResult =
 | |
|             sess->reply.errorCode;
 | |
|         ((ASPWriteRec*)(sess->spCommandRec))->replyLength = dataLength;
 | |
|         ((ASPWriteRec*)(sess->spCommandRec))->writtenLength =
 | |
|             ((ASPWriteRec*)(sess->spCommandRec))->writeDataLength;
 | |
|         break;
 | |
|     }
 | |
| 
 | |
| complete:
 | |
|     CompleteASPCommand(sess, 0);
 | |
| }
 | |
| 
 | |
| /* Actions to complete a command, whether successful or not */
 | |
| void CompleteASPCommand(Session *sess, Word result) {
 | |
|     SPCommandRec *commandRec;
 | |
| 
 | |
|     commandRec = sess->spCommandRec;
 | |
| 
 | |
|     if (sess->spCommandRec->command == aspGetStatusCommand 
 | |
|         || sess->spCommandRec->command == aspCloseSessionCommand)
 | |
|     {
 | |
|         EndSession(sess, FALSE);
 | |
|     } else {
 | |
|         sess->commandPending = FALSE;
 | |
|         if (sess->dsiStatus != error) {
 | |
|             sess->dsiStatus = awaitingHeader;
 | |
|             InitReadTCP(sess, DSI_HEADER_SIZE, &sess->reply);
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     commandRec->result = result;
 | |
|     
 | |
|     if ((commandRec->async & AT_ASYNC) && commandRec->completionPtr != NULL) {
 | |
|         CallCompletionRoutine((void *)commandRec->completionPtr);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| static void EndSession(Session *sess, Boolean callAttnRoutine) {
 | |
|     if (callAttnRoutine) {
 | |
|         // TODO call the attention routine to report end of session
 | |
|     }
 | |
|     
 | |
|     EndTCPConnection(sess);
 | |
|     memset(sess, 0, sizeof(*sess));
 | |
| }
 | |
| 
 | |
| void PollAllSessions(void) {
 | |
|     unsigned int i;
 | |
| 
 | |
|     for (i = 0; i < MAX_SESSIONS; i++) {
 | |
|         if (sessionTbl[i].dsiStatus != unused) {
 | |
|             PollForData(&sessionTbl[i]);
 | |
|         }
 | |
|     }
 | |
| }
 |