From c4286a024ea64e90f6bec0fe8357125660818911 Mon Sep 17 00:00:00 2001 From: Stephen Heumann Date: Sat, 22 Apr 2017 18:46:26 -0500 Subject: [PATCH] Add options to give somewhat better compatibility with the AFP server in OS X 10.5. *The OS X server doesn't support ProDOS-type file type info, so trying to set it will give an error. This normally causes GS/OS to give an error when creating a file. There is now an option to ignore the error and proceed with creating the file, although the file type still won't be set. *The OS X server seems to restrict each write operation to a small size based on the ASP quantum, so there is now an option for this. Using this option means we may write less than requested, but the AppleShare FST can detect this and proceed to write the rest. --- afpcdev.c | 23 +++++++++++---- afpcdev.rez | 43 +++++++++++++++++++++++----- afpoptions.c | 2 ++ afpoptions.h | 8 ++++-- aspinterface.c | 77 +++++++++++++++++++++++++++++++++++++++++++------- 5 files changed, 127 insertions(+), 26 deletions(-) diff --git a/afpcdev.c b/afpcdev.c index 8a25b94..b48369d 100644 --- a/afpcdev.c +++ b/afpcdev.c @@ -46,11 +46,13 @@ #define saveFilePrompt 100 -#define optionsMenu 300 -#define afpOverTCPOptionsItem 301 -#define useLargeReadsItem 302 -#define forceAFP22Item 303 -#define fakeSleepItem 304 +#define optionsMenu 300 +#define afpOverTCPOptionsItem 301 +#define useLargeReadsItem 302 +#define forceAFP22Item 303 +#define fakeSleepItem 304 +#define useLargeWritesItem 305 +#define ignoreErrorsSettingFileTypesItem 306 #define fstMissingError 3000 #define noEasyMountError 3001 @@ -112,7 +114,8 @@ Word modifiers = 0; char zoneBuf[ZONE_MAX + 1]; char AFPOverTCPZone[] = "AFP over TCP"; -unsigned int flags = fLargeReads; /* for AFP over TCP connections */ +/* Default flags for AFP over TCP connections */ +unsigned int flags = fLargeReads | fLargeWrites; void fillEasyMountRec(char *server, char *zone, char *volume, char *user, char *password, char *volpass) @@ -510,6 +513,14 @@ void DoHit(long ctlID, CtlRecHndl ctlHandle) } else if (menuItem == fakeSleepItem) { flags ^= fFakeSleep; CheckMItem((flags & fFakeSleep) ? TRUE : FALSE, fakeSleepItem); + } else if (menuItem == useLargeWritesItem) { + flags ^= fLargeWrites; + CheckMItem((flags & fLargeWrites) ? TRUE : FALSE, + useLargeWritesItem); + } else if (menuItem == ignoreErrorsSettingFileTypesItem) { + flags ^= fIgnoreFileTypeErrors; + CheckMItem((flags & fIgnoreFileTypeErrors) ? TRUE : FALSE, + ignoreErrorsSettingFileTypesItem); } SetCtlValue(afpOverTCPOptionsItem, ctlHandle); diff --git a/afpcdev.rez b/afpcdev.rez index 3e1dd43..9148b5d 100644 --- a/afpcdev.rez +++ b/afpcdev.rez @@ -93,11 +93,13 @@ resource rIcon (1) { #define saveFilePrompt 100 -#define optionsMenu 300 -#define afpOverTCPOptionsItem 301 -#define useLargeReadsItem 302 -#define forceAFP22Item 303 -#define fakeSleepItem 304 +#define optionsMenu 300 +#define afpOverTCPOptionsItem 301 +#define useLargeReadsItem 302 +#define forceAFP22Item 303 +#define fakeSleepItem 304 +#define useLargeWritesItem 305 +#define ignoreErrorsSettingFileTypesItem 306 /* * Controls in the control panel window (for 640 mode or 320 mode) @@ -245,7 +247,14 @@ resource rMenu (optionsMenu) { optionsMenu, /* menu ID */ refIsResource*menuTitleRefShift + refIsResource*itemRefShift, optionsMenu, /* menu title ref (not drawn) */ - {afpOverTCPOptionsItem, useLargeReadsItem, forceAFP22Item, fakeSleepItem}; + { + afpOverTCPOptionsItem, + useLargeReadsItem, + useLargeWritesItem, + forceAFP22Item, + fakeSleepItem, + ignoreErrorsSettingFileTypesItem + }; }; resource rPString(optionsMenu,noCrossBank) { "" }; @@ -266,7 +275,16 @@ resource rMenuItem (useLargeReadsItem) { useLargeReadsItem /* menu item title ref */ }; resource rPString(useLargeReadsItem,noCrossBank) { "Use Large Reads" }; - + +resource rMenuItem (useLargeWritesItem) { + useLargeWritesItem, /* menu item ID */ + "","", + $12, + refIsResource*itemTitleRefShift, + useLargeWritesItem /* menu item title ref */ +}; +resource rPString(useLargeWritesItem,noCrossBank) { "Use Large Writes" }; + resource rMenuItem (forceAFP22Item) { forceAFP22Item, /* menu item ID */ "","", @@ -285,6 +303,17 @@ resource rMenuItem (fakeSleepItem) { }; resource rPString(fakeSleepItem,noCrossBank) { "Fake Sleep to Keep Alive" }; +resource rMenuItem (ignoreErrorsSettingFileTypesItem) { + ignoreErrorsSettingFileTypesItem, /* menu item ID */ + "","", + 0, + refIsResource*itemTitleRefShift, + ignoreErrorsSettingFileTypesItem /* menu item title ref */ +}; +resource rPString(ignoreErrorsSettingFileTypesItem,noCrossBank) { + "Ignore Errors Setting File Types" +}; + /* * Controls in the help window */ diff --git a/afpoptions.c b/afpoptions.c index c132880..43ddfc2 100644 --- a/afpoptions.c +++ b/afpoptions.c @@ -10,7 +10,9 @@ AFPOptions afpOptions[] = { {"LR", fLargeReads}, + {"LW", fLargeWrites}, {"22", fForceAFP22}, {"FS", fFakeSleep}, + {"IE", fIgnoreFileTypeErrors}, {0, 0} }; diff --git a/afpoptions.h b/afpoptions.h index 548bc17..bcb15dd 100644 --- a/afpoptions.h +++ b/afpoptions.h @@ -1,9 +1,11 @@ #ifndef AFPOPTIONS_H #define AFPOPTIONS_H -#define fLargeReads 0x0001 -#define fForceAFP22 0x0002 -#define fFakeSleep 0x0004 +#define fLargeReads 0x0001 +#define fForceAFP22 0x0002 +#define fFakeSleep 0x0004 +#define fIgnoreFileTypeErrors 0x0008 +#define fLargeWrites 0x0010 typedef struct AFPOptions { char *optString; diff --git a/aspinterface.c b/aspinterface.c index 547714e..5c1e04d 100644 --- a/aspinterface.c +++ b/aspinterface.c @@ -26,9 +26,32 @@ typedef struct FPReadRec { Byte NewLineChar; } FPReadRec; +typedef struct FPWriteRec { + Word CommandCode; /* includes pad byte */ + Word OForkRecNum; + LongWord Offset; + LongWord ReqCount; +} FPWriteRec; + +typedef struct FPSetFileDirParmsRec { + Word CommandCode; /* includes pad byte */ + Word VolumeID; + LongWord DirectoryID; + Word Bitmap; + Byte PathType; + /* Pathname and parameters follow */ +} FPSetFileDirParmsRec; + #define kFPRead 27 #define kFPLogin 18 #define kFPZzzzz 122 +#define kFPSetFileDirParms 35 + +#define kFPBitmapErr (-5004L) + +#define kFPProDOSInfoBit 0x2000 /* In file/directory bitmaps */ + +#define ASPQuantumSize 4624 /* For forced AFP 2.2 login */ static Byte loginBuf[100]; @@ -196,13 +219,13 @@ ret: * 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. + * This implementation differs from Apple's specifications in a couple + * respects, but matches the actual behavior of the servers I've tried: + * + * -Apple's docs say FPZzzzz was introduced with AFP 2.3, but Netatalk and + * OS X 10.5 support it with AFP 2.2 and doesn't support AFP 2.3 at all. + * -Apple's docs show no reply block for FPZzzzz, but Netatalk and + * OS X 10.5 send a reply with four bytes of data. */ if ((sess->atipMapping.flags & fFakeSleep) && sess->loggedIn && commandRec->result == 0 @@ -355,12 +378,27 @@ static void DoSPCommand(Session *sess, ASPCommandRec *commandRec) { } static void DoSPWrite(Session *sess, ASPWriteRec *commandRec) { + LongWord dataLength; + 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); + dataLength = commandRec->writeDataLength; + + /* + * If requested, limit the data size of a single DSIWrite command to 4623. + * This is necessary to make writes work on OS X (and others?). + */ + if (!(sess->atipMapping.flags & fLargeWrites) + && dataLength > ASPQuantumSize - 1) + { + dataLength = ASPQuantumSize - 1; + ((FPReadRec*)commandRec->cmdBlkAddr)->ReqCount = htonl(dataLength); + } + + sess->request.totalDataLength = + htonl(dataLength + commandRec->cmdBlkLength); sess->replyBuf = (void*)(commandRec->replyBufferAddr & 0x00FFFFFF); sess->replyBufLen = commandRec->replyBufferLen; @@ -411,13 +449,32 @@ void FinishASPCommand(Session *sess) { ((ASPCommandRec*)(sess->spCommandRec))->cmdResult = sess->reply.errorCode; ((ASPCommandRec*)(sess->spCommandRec))->replyLength = dataLength; + + /* + * If requested by user, ignore errors when setting ProDOS-style + * filetype info. This allows files to be written to servers running + * old versions of OS X, although filetypes are not set correctly. + */ + if ((sess->atipMapping.flags & fIgnoreFileTypeErrors) + && ((ASPCommandRec*)(sess->spCommandRec))->cmdBlkLength + > sizeof(FPSetFileDirParmsRec) + && *(Byte*)(((ASPCommandRec*)(sess->spCommandRec))->cmdBlkAddr) + == kFPSetFileDirParms + && (((FPSetFileDirParmsRec*)(((ASPCommandRec*)(sess->spCommandRec)) + ->cmdBlkAddr))->Bitmap & htons(kFPProDOSInfoBit)) + && sess->reply.errorCode == htonl((LongWord)kFPBitmapErr)) + { + ((ASPCommandRec*)(sess->spCommandRec))->cmdResult = 0; + } + break; case aspWriteCommand: ((ASPWriteRec*)(sess->spCommandRec))->cmdResult = sess->reply.errorCode; ((ASPWriteRec*)(sess->spCommandRec))->replyLength = dataLength; ((ASPWriteRec*)(sess->spCommandRec))->writtenLength = - ((ASPWriteRec*)(sess->spCommandRec))->writeDataLength; + ntohl(sess->request.totalDataLength) + - ((ASPWriteRec*)(sess->spCommandRec))->cmdBlkLength; break; }