Retired Document
Important: This sample code may not represent best practices for current development. The project may use deprecated symbols and illustrate technologies and techniques that are no longer recommended.
VMSify.c
/* |
File: VMSify.c |
Contains: Stream module that does nothing. |
Written by: Quinn "The Eskimo!" |
Copyright: © 1997 by Apple Computer, Inc., all rights reserved. |
Change History (most recent first): |
You may incorporate this sample code into your applications without |
restriction, though the sample code has been provided "AS IS" and the |
responsibility for its operation is 100% yours. However, what you are |
not permitted to do is to redistribute the source as "DSC Sample Code" |
after having made changes. If you're going to re-distribute the source, |
we require that you make it clear in the source that the code was |
descended from Apple Sample Code, but that you've made changes. |
*/ |
/* |
A Note About States |
------------------- |
Throughout the source, you'll see comments like: |
sta_X + Y -> sta_Z |
These comments describe state transitions in the TPI state machine, |
which is documented in Figure 9 of the TPI specification: |
<http://developer.apple.com/macos/opentransport/docs/dev/tpi.pdf> |
The basic idea is that, when you start in state X and get event Y, |
you transition to state Z. |
This module has to track the state of the provider to ensure that it |
only turns on the VMSify'ing functionality when the provider is being |
used for outgoing streams connected to the telnet port. |
*/ |
///////////////////////////////////////////////////////////////////// |
// The OT debugging macros in <OTDebug.h> require this variable to |
// be set. |
#ifndef qDebug |
#define qDebug 0 |
#endif |
///////////////////////////////////////////////////////////////////// |
// Pick up all the standard OT module stuff. |
#include <OpenTransportKernel.h> |
#include <OpenTransportProviders.h> |
///////////////////////////////////////////////////////////////////// |
// OpenTptMiscUtilsPPC.o is not exporting OTDebugStr currently |
// (Universal Interfaces 3.3.1). So we just define our own version |
// here. |
extern pascal void OTDebugStr(const char *msg) |
{ |
debugstr(msg); |
} |
///////////////////////////////////////////////////////////////////// |
// Some simple routines we use in our various assertions. |
static Boolean IsReadQ(queue_t* q) |
// Returns true if q is the read queue of a queue pair. |
{ |
return ((q->q_flag & QREADR) != 0); |
} |
static Boolean IsWriteQ(queue_t* q) |
// Returns true if q is the write queue of a queue pair. |
{ |
return ((q->q_flag & QREADR) == 0); |
} |
///////////////////////////////////////////////////////////////////// |
// Per-Stream information |
enum { |
kTelnetPort = 23 |
}; |
// This structure is used to hold the per-stream data for the module. |
// While module's can use normal global variables to store real globals, |
// they must maintain their own per-stream data structures. I use |
// mi_open_comm to allocate this data structure when the stream is |
// opened. mi_open_comm stores the address of this data structure in the |
// read and write queue's q_ptr field, so the rest of the code |
// can get to it by calling the GetPerStreamData function. |
enum { |
kVMSifyPerStreamDataMagic = 'VMS!' |
}; |
struct PerStreamData |
{ |
OSType magic; // kVMSifyPerStreamDataMagic = 'VMS!' for debugging |
// Your per-stream data structures go here. |
InetPort lastConnReqPort; // outgoing connection requests setup this |
Boolean vmsify; // if we made a successful outgoing connection to port kTelnetPort, this is true |
}; |
typedef struct PerStreamData PerStreamData, *PerStreamDataPtr; |
static PerStreamDataPtr GetPerStreamData(queue_t* readOrWriteQ) |
// You can pass both the read or the write queue to this routine |
// because mi_open_comm sets up both q_ptr's to point to the |
// queue local data. |
// |
// Note that, in order to avoid the overhead of a function call, |
// you would normally use inline code (or a macro) |
// to get your per-stream data instead of using a separate function. |
// However I think the separate function makes things clearer. |
// I also acts as a central bottleneck for my debugging code. |
// |
// Environment: any standard STREAMS entry point |
{ |
PerStreamDataPtr streamData; |
streamData = (PerStreamDataPtr) readOrWriteQ->q_ptr; |
OTAssert("GetPerStreamData: what streamData", streamData != nil); |
OTAssert("GetPerStreamData: Bad magic", streamData->magic == kVMSifyPerStreamDataMagic); |
return streamData; |
} |
// mi_open_comm and mi_close_comm (and also mi_detach and mi_close_detached) |
// use this global to store the list of open streams to this module. |
static char* gStreamList = nil; |
///////////////////////////////////////////////////////////////////// |
// Open routine |
static SInt32 VMSifyOpen(queue_t* rdq, dev_t* dev, SInt32 flag, SInt32 sflag, cred_t* creds) |
// This routine is called by STREAMS when a new stream is connected to |
// our module. The bulk of the work here is done by the Mentat helper |
// routine mi_open_comm. |
// |
// Environment: standard STREAMS entry point |
{ |
SInt32 err; |
PerStreamDataPtr streamData; |
OTAssert("VMSifyOpen: Not the read queue", IsReadQ(rdq) ); |
// OTDebugBreak("VMSifyOpen"); |
err = noErr; |
// If we already have per-stream data for this stream, the stream is being reopened. |
// In that case, we can just return. |
// Note that we can't call GetPerStreamData because it checks that streamData is not nil. |
if ( rdq->q_ptr != nil ) { |
goto done; |
} |
// Make sure we're being opened properly -- because we're a module we |
// require a "module" open. Other possibilities are the value 0 (used |
// to open a specific minor device number (ie stream) on a device driver), |
// and CLONEOPEN (used to open a new stream to a device number where you |
// don't care what device number you get -- the typical behaviour for |
// networking (as opposed to serial) devices). |
if ( (err == noErr) && (sflag != MODOPEN) ) { |
err = ENXIO; |
} |
// Use the mi_open_comm routine to allocate our per-stream data. Then |
// zero out the entire per-stream data record and fill out the fields |
// we're going to need. |
if (err == noErr) { |
err = mi_open_comm(&gStreamList, sizeof(PerStreamData), rdq, dev, flag, sflag, creds); |
if ( err == noErr ) { |
// Note that we can't call GetPerStreamData because the magic is not set up yet. |
streamData = (PerStreamDataPtr) rdq->q_ptr; |
OTMemzero(streamData, sizeof(PerStreamData)); |
streamData->magic = kVMSifyPerStreamDataMagic; |
streamData->lastConnReqPort = 0; |
streamData->vmsify = false; |
} |
} |
done: |
return err; |
} |
///////////////////////////////////////////////////////////////////// |
// Close routine |
static SInt32 VMSifyClose(queue_t* rdq, SInt32 flags, cred_t* credP) |
// This routine is called by STREAMS when a stream is being |
// disconnected from our module (ie closed). The bulk of the work |
// is done by the magic Mentat helper routine mi_close_comm. |
// |
// Environment: standard STREAMS entry point |
{ |
#pragma unused(flags) |
#pragma unused(credP) |
OTAssert("VMSifyClose: Not the read queue", IsReadQ(rdq) ); |
(void) mi_close_comm(&gStreamList, rdq); |
return 0; |
} |
///////////////////////////////////////////////////////////////////// |
enum { |
kNoPrimitive = -1 |
}; |
static long GetPrimitive(mblk_t* mp) |
// GetPrimitive gets the TPI/DLPI primitive out of a message block. |
// It returns kNoPrimitive if the message block is of the wrong |
// type or there is no primitive. |
// |
// Environment: any standard STREAMS entry point |
{ |
if ((mp->b_datap->db_type == M_PROTO || mp->b_datap->db_type == M_PCPROTO) && MBLK_SIZE(mp) >= sizeof(long) ) { |
return ( (union T_primitives*) mp->b_rptr)->type; |
} else { |
return kNoPrimitive; |
} |
} |
///////////////////////////////////////////////////////////////////// |
// Write-side put routine |
static InetPort ExtractPortFromDNSAddress(DNSAddress *dnsAddr, ByteCount DEST_length) |
// Parse the port number out of a DNSAddress structure. |
// Yetch! |
{ |
InetPort portNum; |
char *cursor; |
char *limit; |
portNum = 0; |
cursor = dnsAddr->fName; |
limit = ((char *) dnsAddr) + DEST_length; |
// Skip past all the stuff before the colon. |
while ( cursor < limit && *cursor != ':' ) { |
cursor += 1; |
} |
// If there's a colon, skip over it and then |
// extract digits from there. |
if (*cursor == ':') { |
cursor += 1; // skip past ':' |
while (cursor < limit && *cursor >= '0' && *cursor <= '9') { |
portNum = (portNum * 10) + (*cursor - '0'); |
cursor += 1; |
} |
} |
return portNum; |
} |
static SInt32 VMSifyWritePut(queue_t* q, mblk_t* mp) |
// This routine is called by STREAMS when it has a message for our |
// module from upstream. Typically, this routine is a big case statement |
// that dispatches to our various message handling routines. |
// |
// Environment: standard STREAMS entry point |
{ |
PerStreamDataPtr streamData; |
T_conn_req *connReq; |
OTAddress *otAddr; |
InetAddress *inetAddr; |
OTAssert("VMSifyWritePut: Not the write queue", IsWriteQ(q) ); |
// OTDebugBreak("VMSifyWritePut: Entered"); |
streamData = GetPerStreamData(q); |
switch ( GetPrimitive(mp) ) { |
case T_CONN_REQ: |
OTAssert("VMSifyWritePut: We got connection request but we still think we're connected", streamData->lastConnReqPort == 0); |
OTAssert("VMSifyWritePut: We got connection request but we still think we're VMSifying", !streamData->vmsify); |
connReq = (T_conn_req *) mp->b_rptr; |
otAddr = (OTAddress *) (mp->b_rptr + connReq->DEST_offset); |
switch (otAddr->fAddressType) { |
case AF_DNS: |
streamData->lastConnReqPort = ExtractPortFromDNSAddress( (DNSAddress *) otAddr, connReq->DEST_length); |
break; |
case AF_INET: |
OTAssert("VMSifyWritePut: Client passed bad address length, we're may make a bad decision, but TCP/IP will probably barf anyway", connReq->DEST_length == sizeof(InetAddress)); |
inetAddr = (InetAddress *) otAddr; |
streamData->lastConnReqPort = inetAddr->fPort; |
break; |
default: |
OTDebugBreak("VMSifyWritePut: Bogus address format"); |
streamData->lastConnReqPort = 0; |
break; |
} |
putnext(q, mp); |
break; |
default: |
putnext(q, mp); |
break; |
} |
return 0; |
} |
///////////////////////////////////////////////////////////////////// |
// Read-side put routine |
static void VMSifyMessageChain(mblk_t **destPtr) |
// Given a pointer to a pointer to a chain of messages |
// (destPtr), replace the chain of messages with a |
// chain of buffers where the data has been uppercased. |
// This might involve copying the messages if the data |
// they reference is read only, hence we pass in the |
// address of the head of the chain, not the head |
// of the chain itself. |
{ |
mblk_t *source; |
OTAssert("VMSifyMessageChain: Bad dest pointer", destPtr != nil); |
source = *destPtr; |
while ( source != nil ) { |
mblk_t *next; |
mblk_t *tmp; |
OTAssert("VMSifyReadPut: Oh dear", *destPtr != nil); |
// Extract next here because we might have freed |
// source by the time we get to the end of the |
// routine. |
next = source->b_cont; |
// See whether the source buffer is writable. |
// If it is, setup tmp so that we munge it. |
// If it isn't, try to copy the buffer. If |
// that works, setup tmp so that we munge |
// the copy, and free the original. If it |
// fails, leave tmp set to nil so that we |
// just pass through the unmunged buffer, |
// which is not necessarily the best response |
// to an out of memory situation, but it |
// certainly is easy to implement (-: |
if (source->b_datap->db_ref == 1) { |
tmp = source; |
} else { |
tmp = copyb(source); |
if (tmp != nil) { |
freeb(source); |
} |
} |
// We we can write to the buffer, upper case |
// it and chain it on to the destination list. |
// If we can't write to it, just chain the |
// unmunged buffer on to the destination list. |
if (tmp != nil) { |
UInt8 *cursor; |
UInt8 ch; |
OTAssert("VMSifyMessageChain: Buffer is still not writeable", tmp->b_datap->db_ref == 1); |
for (cursor = tmp->b_rptr; cursor < tmp->b_wptr; cursor++) { |
ch = *cursor; |
if (ch >= 'a' && ch <= 'z') { |
ch -= 0x20; |
*cursor = ch; |
} |
} |
*destPtr = tmp; |
} else { |
*destPtr = source; |
} |
// Move to the next buffer on both the |
// source and destination chains. |
destPtr = &((*destPtr)->b_cont); |
source = next; |
} |
} |
static SInt32 VMSifyReadPut(queue_t* q, mblk_t* mp) |
// This routine is called by STREAMS when it has a message for our |
// module from downstream. Typically, this routine is a big case statement |
// that dispatches to our various message handling routines. |
// |
// Environment: standard STREAMS entry point |
{ |
PerStreamDataPtr streamData; |
Boolean okAck; |
OTAssert("VMSifyReadPut: Not the read queue", IsReadQ(q) ); |
// OTDebugBreak("VMSifyReadPut: Entered"); |
streamData = GetPerStreamData(q); |
switch ( mp->b_datap->db_type ) { |
case M_DATA: |
if ( streamData->vmsify ) { |
VMSifyMessageChain(&mp); |
} |
putnext(q, mp); |
break; |
default: |
switch ( GetPrimitive(mp) ) { |
case T_OK_ACK: |
case T_ERROR_ACK: |
okAck = (GetPrimitive(mp) == T_OK_ACK); |
switch ( ((T_ok_ack *) mp->b_rptr)->CORRECT_prim ) { |
case T_CONN_REQ: |
if (okAck) { |
// sta_5 + ok_ack1 -> sta_6 |
// Connection in place, let's start vmsifying if required. |
streamData->vmsify = (streamData->lastConnReqPort == kTelnetPort); |
} else { |
// sta_5 + error_ack -> sta_3 |
// Connection attemp failed, leave vmsify false. |
OTAssert("VMSifyReadPut: vmsify should be false but isn't", !streamData->vmsify); |
} |
streamData->lastConnReqPort = 0; |
break; |
case T_DISCON_REQ: |
if (okAck) { |
// sta_14 + ok_ack1 -> sta_3 |
// sta_15 + ok_ack1 -> sta_3 |
// sta_16 + ok_ack1 -> sta_3 |
// Disconnect successful, connection is gone, clear vmsify. |
streamData->vmsify = false; |
} else { |
// sta_14 + error_ack -> sta_9 |
// sta_15 + error_ack -> sta_10 |
// sta_16 + error_ack -> sta_11 |
// Disconnect failed, connection is still in place, |
// so is vmsify. |
} |
break; |
default: |
break; |
} |
putnext(q, mp); |
break; |
case T_ORDREL_IND: |
// sta_10 + ordrel_ind -> sta_3 |
// sta_9 + ordrel_ind -> sta_11 |
streamData->vmsify = false; |
putnext(q, mp); |
break; |
case T_DISCON_IND: |
// sta_6 + discon_ind1 -> sta_3 |
// sta_9 + discon_ind1 -> sta_3 |
// sta_10 + discon_ind1 -> sta_3 |
// sta_11 + discon_ind1 -> sta_3 |
// sta_7 + discon_ind1 -> sta_3 |
// sta_7 + discon_ind1 -> sta_7 ¥¥¥ÊThis is a weird one, probably need to analyse this more. ¥¥¥ |
streamData->vmsify = false; |
putnext(q, mp); |
break; |
case T_DATA_IND: |
if ( streamData->vmsify ) { |
VMSifyMessageChain(&mp->b_cont); |
} |
putnext(q, mp); |
break; |
default: |
putnext(q, mp); |
break; |
} |
break; |
} |
return 0; |
} |
///////////////////////////////////////////////////////////////////// |
// Static Declaration Structures |
static struct module_info gModuleInfo = |
{ |
9992, // Module Number, only useful for debugging |
"VMSify", // Name of module |
0, // Minimum data size |
INFPSZ, // Maximum data size |
16384, // Hi water mark for queue |
4096 // Lo water mark for queue |
}; |
static struct qinit gReadInit = |
{ |
VMSifyReadPut, // Put routine for "incoming" data |
nil, // Service routine for "incoming" data |
VMSifyOpen, // Our open routine |
VMSifyClose, // Our close routine |
nil, // No admin routine |
&gModuleInfo // Our module_info |
}; |
static struct qinit gWriteInit = |
{ |
VMSifyWritePut, // Put routine for client data |
nil, // Service routine for client data |
nil, // open field only used in read-side structure |
nil, // close field only used in read-side structure |
nil, // admin field only used in read-side structure |
&gModuleInfo // Our module_info |
}; |
static struct streamtab theStreamTab = |
{ |
&gReadInit, // Our read-side qinit structure |
&gWriteInit, // Our write-side qinit structure |
0, // We are not a mux, so set this to nil |
0 // We are not a mux, so set this to nil |
}; |
///////////////////////////////////////////////////////////////////// |
// Macintosh-specific Static Structures |
static struct install_info theInstallInfo = |
{ |
&theStreamTab, // Stream Tab pointer |
kOTModIsModule + kOTModUpperIsTPI + kOTModIsFilter, |
// Tell OT that we are a driver, not a module |
SQLVL_MODULE, // Synchronization level, module level for the moment |
0, // Shared writer list buddy |
0, // Open Transport use - always set to 0 |
0 // Flag - always set to 0 |
}; |
// Prototypes for the exported routines below. |
extern Boolean InitStreamModule(void *portInfo); |
extern void TerminateStreamModule(void); |
extern install_info* GetOTInstallInfo(); |
#pragma export list InitStreamModule, TerminateStreamModule, GetOTInstallInfo |
// Export entry point |
extern Boolean InitStreamModule(void *portInfo) |
// Initialises the module before the first stream is opened. |
// Should return true if the module has started up correctly. |
// |
// Environment: Always called at SystemTask time. |
{ |
#pragma unused(portInfo) |
Boolean result; |
OTDebugBreak("VMSify: InitStreamModule"); |
result = true; |
return result; |
} |
extern void TerminateStreamModule(void) |
// Shuts down the module after the last stream has been |
// closed. |
// |
// Environment: Always called at SystemTask time. |
{ |
// It's an excellent idea to have the following in your code, just to make |
// sure you haven't left any streams open before you quit. In theory, OT |
// should not call you until the last stream has been closed, but in practice |
// this can happen if you use mi_detach to half-close a stream. |
OTAssert("TerminateStreamModule: Streams are still active", gStreamList == nil); |
} |
extern install_info* GetOTInstallInfo() |
// Return pointer to install_info to STREAMS. |
{ |
return &theInstallInfo; |
} |
Copyright © 2003 Apple Computer, Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2003-07-22