From 78a300f799d9222a6d3ff4e11e05e20eb1471b72 Mon Sep 17 00:00:00 2001 From: Stephen Heumann Date: Fri, 21 Apr 2017 21:09:57 -0500 Subject: [PATCH] Add an option to tell the server that the GS is going to sleep after each command sent. This tells the server to keep the session alive (typically for at least several hours) without sending or expecting tickles. This is useful in cases where our run queue procedure is not run regularly or at all, e.g. in text-mode shells. The implementation here is geared toward compatibility with Netatalk and differs from Apple's published standards in a couple ways. It may or may not work with other servers. --- afpcdev.c | 13 +++++++++++++ afpcdev.rez | 12 +++++++++++- afpoptions.c | 2 ++ afpoptions.h | 1 + aspinterface.c | 42 +++++++++++++++++++++++++++++++++++++++++- cmdproc.asm | 13 +++++++++++++ cmdproc.h | 2 ++ installcmds.c | 1 + savenames.c | 17 +++++++++++++++++ session.h | 14 ++++++++++++++ 10 files changed, 115 insertions(+), 2 deletions(-) diff --git a/afpcdev.c b/afpcdev.c index c646857..1adc84c 100644 --- a/afpcdev.c +++ b/afpcdev.c @@ -50,6 +50,7 @@ #define afpOverTCPOptionsItem 301 #define useLargeReadsItem 302 #define forceAFP22Item 303 +#define fakeSleepItem 304 #define fstMissingError 3000 #define noEasyMountError 3001 @@ -492,6 +493,18 @@ void DoHit(long ctlID, CtlRecHndl ctlHandle) } else if (menuItem == forceAFP22Item) { flags ^= fForceAFP22; CheckMItem((flags & fForceAFP22) ? TRUE : FALSE, forceAFP22Item); + + /* Fake sleep is allowed only with AFP 2.2. */ + if (flags & fForceAFP22) { + EnableMItem(fakeSleepItem); + } else { + DisableMItem(fakeSleepItem); + CheckMItem(FALSE, fakeSleepItem); + flags &= ~fFakeSleep; + } + } else if (menuItem == fakeSleepItem) { + flags ^= fFakeSleep; + CheckMItem((flags & fFakeSleep) ? TRUE : FALSE, fakeSleepItem); } SetCtlValue(afpOverTCPOptionsItem, ctlHandle); diff --git a/afpcdev.rez b/afpcdev.rez index 057746c..8b8418f 100644 --- a/afpcdev.rez +++ b/afpcdev.rez @@ -97,6 +97,7 @@ resource rIcon (1) { #define afpOverTCPOptionsItem 301 #define useLargeReadsItem 302 #define forceAFP22Item 303 +#define fakeSleepItem 304 /* * Controls in the control panel window (for 640 mode or 320 mode) @@ -244,7 +245,7 @@ resource rMenu (optionsMenu) { optionsMenu, /* menu ID */ refIsResource*menuTitleRefShift + refIsResource*itemRefShift, optionsMenu, /* menu title ref (not drawn) */ - {afpOverTCPOptionsItem, useLargeReadsItem, forceAFP22Item}; + {afpOverTCPOptionsItem, useLargeReadsItem, forceAFP22Item, fakeSleepItem}; }; resource rPString(optionsMenu,noCrossBank) { "" }; @@ -275,6 +276,15 @@ resource rMenuItem (forceAFP22Item) { }; resource rPString(forceAFP22Item,noCrossBank) { "Force AFP Version 2.2" }; +resource rMenuItem (fakeSleepItem) { + fakeSleepItem, /* menu item ID */ + "","", + 0, + fDisabled+refIsResource*itemTitleRefShift, + fakeSleepItem /* menu item title ref */ +}; +resource rPString(fakeSleepItem,noCrossBank) { "Fake Sleep to Keep Alive" }; + /* * Controls in the help window */ diff --git a/afpoptions.c b/afpoptions.c index 1246442..fe809db 100644 --- a/afpoptions.c +++ b/afpoptions.c @@ -9,5 +9,7 @@ AFPOptions afpOptions[] = {"AFP over TCP (LargeReads)", fLargeReads}, {"AFP over TCP (AFP2.2)", fForceAFP22}, {"AFP over TCP (LargeReads,AFP2.2)", fLargeReads | fForceAFP22}, + {"AFP over TCP (AFP2.2,FakeSleep)", fForceAFP22 | fFakeSleep}, + {"AFP over TCP (LR,AFP2.2,FS)", fLargeReads | fForceAFP22 | fFakeSleep}, {0, 0} }; diff --git a/afpoptions.h b/afpoptions.h index 00de274..a289374 100644 --- a/afpoptions.h +++ b/afpoptions.h @@ -3,6 +3,7 @@ #define fLargeReads 0x0001 #define fForceAFP22 0x0002 +#define fFakeSleep 0x0004 typedef struct AFPOptions { char *zoneString; diff --git a/aspinterface.c b/aspinterface.c index ae00954..547714e 100644 --- a/aspinterface.c +++ b/aspinterface.c @@ -28,6 +28,7 @@ typedef struct FPReadRec { #define kFPRead 27 #define kFPLogin 18 +#define kFPZzzzz 122 /* For forced AFP 2.2 login */ static Byte loginBuf[100]; @@ -41,6 +42,7 @@ static void DoSPCommand(Session *sess, ASPCommandRec *commandRec); static void DoSPWrite(Session *sess, ASPWriteRec *commandRec); static void CompleteASPCommand(SPCommandRec *commandRec, Word result); +static void InitSleepRec(Session *sess, Byte sessRefNum); Session sessionTbl[MAX_SESSIONS]; @@ -54,6 +56,7 @@ LongWord DispatchASPCommand(SPCommandRec *commandRec) { stateReg = ForceRomIn(); +top: if (commandRec->command == aspGetStatusCommand || commandRec->command==aspOpenSessionCommand) { @@ -84,6 +87,7 @@ LongWord DispatchASPCommand(SPCommandRec *commandRec) { sess = &sessionTbl[i]; sess->spCommandRec = commandRec; sess->commandPending = TRUE; + InitSleepRec(sess, i + SESSION_NUM_START); if ((i = StartTCPConnection(sess)) != 0) { FlagFatalError(sess, i); @@ -186,7 +190,30 @@ LongWord DispatchASPCommand(SPCommandRec *commandRec) { } } -ret: +ret: + /* + * If requested by user, we tell the server we are "going to sleep" + * after every command we send. This avoids having the server + * disconnect us because we can't send tickles for a while. + * + * This implementation is designed to work with Netatalk: + * -Apple's docs say FPZzzzz was introduced with AFP 2.3, but Netatalk + * supports it with AFP 2.2 and doesn't support AFP 2.3 at all. + * -Apple's docs also say there is no reply to FPZzzzz, but Netatalk + * send a reply with four bytes of data. + * Netatalk includes comments indicating Apple's implementation in OS X + * may really work more like Netatalk, but I haven't checked this. + */ + if ((sess->atipMapping.flags & fFakeSleep) + && sess->loggedIn && commandRec->result == 0 + && (commandRec->command == aspCommandCommand + || commandRec->command == aspWriteCommand) + && commandRec != (SPCommandRec*)&sess->sleepCommandRec) + { + commandRec = (SPCommandRec*)&sess->sleepCommandRec; + goto top; + } + RestoreStateReg(stateReg); return 0; @@ -454,6 +481,19 @@ void CallAttentionRoutine(Session *sess, Byte attenType, Word atten) { CallCompletionRoutine((void *)(sess->attention + 1)); } +static void InitSleepRec(Session *sess, Byte sessRefNum) { + sess->sleepCommandRec.async = 0; + sess->sleepCommandRec.command = aspCommandCommand; + sess->sleepCommandRec.completionPtr = 0; + sess->sleepCommandRec.refNum = sessRefNum; + sess->sleepCommandRec.cmdBlkLength = sizeof(sess->fpZzzzzRec); + sess->sleepCommandRec.cmdBlkAddr = (LongWord)&sess->fpZzzzzRec; + sess->sleepCommandRec.replyBufferLen = sizeof(sess->fpZzzzzResponseRec); + sess->sleepCommandRec.replyBufferAddr = (LongWord)&sess->fpZzzzzResponseRec; + sess->fpZzzzzRec.CommandCode = kFPZzzzz; + sess->fpZzzzzRec.Flag = 0; +} + void PollAllSessions(void) { unsigned int i; diff --git a/cmdproc.asm b/cmdproc.asm index 817e846..5ad3d93 100644 --- a/cmdproc.asm +++ b/cmdproc.asm @@ -88,6 +88,19 @@ jslOldPFIListSessions2 entry rtl end +pfiLoginContCmdProc start + lda cmdRecPtr+2 + pha + lda cmdRecPtr + pha +jslOldPFILoginCont entry + jsl jslOldPFILoginCont ; to be changed + rep #$30 ; ensure full native mode + jsl PostLoginCont + rtl + end + + CallCompletionRoutine start phb jsl ForceLCBank2 ;use LC bank 2 diff --git a/cmdproc.h b/cmdproc.h index 996f917..e49a316 100644 --- a/cmdproc.h +++ b/cmdproc.h @@ -7,6 +7,8 @@ void pfiLoginCmdProc(void); extern LongWord jslOldPFILogin; void pfiLogin2CmdProc(void); extern LongWord jslOldPFILogin2; +void pfiLoginContCmdProc(void); +extern LongWord jslOldPFILoginCont; void pfiListSessions2CmdProc(void); extern LongWord jslOldPFIListSessions2; void CallCompletionRoutine(void *); diff --git a/installcmds.c b/installcmds.c index 0437140..14b60fb 100644 --- a/installcmds.c +++ b/installcmds.c @@ -21,6 +21,7 @@ NewCmd newCmds[] = { {nbpLookupNameCommand, nbpCmdProc, NULL}, {pfiLoginCommand, pfiLoginCmdProc, &jslOldPFILogin}, {pfiLogin2Command, pfiLogin2CmdProc, &jslOldPFILogin2}, + {pfiLoginContCommand, pfiLoginContCmdProc, &jslOldPFILoginCont}, {pfiListSessions2Command, pfiListSessions2CmdProc, &jslOldPFIListSessions2}, {0, 0, 0} }; diff --git a/savenames.c b/savenames.c index a63707a..5bdb018 100644 --- a/savenames.c +++ b/savenames.c @@ -87,6 +87,10 @@ void SaveNames(PFILogin2Rec *commandRec) { goto ret; } + if (commandRec->result == 0 && commandRec->sessRefID >= SESSION_NUM_START) { + sessionTbl[commandRec->sessRefID - SESSION_NUM_START].loggedIn = TRUE; + } + if (commandRec->sessRefID <= MAX_ASP_SESSION_NUM) { nameRec = &aspSessionNames[commandRec->sessRefID]; aspActiveFlags[commandRec->sessRefID] = 1; @@ -154,3 +158,16 @@ ret: } #pragma databank 0 +#pragma databank 1 +void PostLoginCont(PFILoginContRec *commandRec) { + Word stateReg; + + stateReg = ForceRomIn(); + + if (commandRec->result == 0 && commandRec->sessRefID >= SESSION_NUM_START) { + sessionTbl[commandRec->sessRefID - SESSION_NUM_START].loggedIn = TRUE; + } + + RestoreStateReg(stateReg); +} +#pragma databank 0 diff --git a/session.h b/session.h index 95b240b..cb0b51b 100644 --- a/session.h +++ b/session.h @@ -2,6 +2,7 @@ #define SESSION_H #include +#include #include "dsiproto.h" #include "atipmapping.h" @@ -23,6 +24,11 @@ typedef enum DSISessionStatus { needsReset /* set after control-reset in P8 mode */ } DSISessionStatus; +typedef struct FPZzzzzRec { + Word CommandCode; /* includes pad byte */ + LongWord Flag; +} FPZzzzzRec; + typedef struct Session { /* Marinetti TCP connection status */ Word ipid; @@ -62,6 +68,14 @@ typedef struct Session { /* Attention routine header (followed by the routine) */ ASPAttentionHeaderRec *attention; + + /* Is this session successfully logged in? */ + Boolean loggedIn; + + /* Records for sending (fake) sleep messages to server */ + ASPCommandRec sleepCommandRec; + FPZzzzzRec fpZzzzzRec; + Byte fpZzzzzResponseRec[4]; } Session; #endif