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; }