/*****************************************************************************/ /* netIO.c The fundamental structure and code for the a/synchronous $QIO with the network. If the NETIO structure contain a Transport Layer Security (secure-sockets) pointer it implicitly uses the TLS/SSL encrypted equivalent. If the NEIO struct contains a pointer to an HTTP/2 stream struct then uses the HTTP/2 pointer within that to perform HTTP/2 I/O (of course the HTTP/2 I/O ultimately uses a NETIO struct to mediate the actual I/O, most often via TLS/SSL). Reading and writing data size now have no architectural limit. It's handled internally. Same for SSL/TLS. With the potential for data size/length greater than a single QIO the IO status block is deprecated in favour of |ioptr->Read/WriteCount| and |ioptr->Read/WriteStatus|. VERSION HISTORY --------------- 20-JAN-2018 MGD refactor NetPeek() into NetIoPeek() NetIoRead() and NetIoWrite() ..IOsb.Status = 0 NetIoRead() and NetIoWrite() blocking NetIo..Ast() each I/O 11-AUG-2015 MGD restructure of network I/O abstractions */ /*****************************************************************************/ #ifdef WASD_VMS_V7 # undef __VMS_VER # define __VMS_VER 70000000 # undef __CRTL_VER # define __CRTL_VER 70000000 #else # ifdef WASD_VMS_V7 # undef _VMS__V6__SOURCE # define _VMS__V6__SOURCE # undef __VMS_VER # define __VMS_VER 70000000 # undef __CRTL_VER # define __CRTL_VER 70000000 # endif #endif #include #include #include #include "wasd.h" #define WASD_MODULE "NETIO" /******************/ /* global storage */ /******************/ /********************/ /* external storage */ /********************/ extern uint EfnWait, EfnNoWait, HttpdTickSecond; extern char ErrorSanityCheck[]; extern struct dsc$descriptor TcpIpDeviceDsc; extern ACCOUNTING_STRUCT *AccountingPtr; extern CONFIG_STRUCT Config; extern WATCH_STRUCT Watch; /*****************************************************************************/ /* Allocate (global) memory for the network I/O structure and assign a channel to the internet template device. Return a pointer to the structure if successful or NULL if not. At a maximum of every second or so reports any channel assignment failure until the maximum period is exceeded (currently one minute). */ NETIO_STRUCT* NetIoBegin () { static ulong ExitTickSecond, PrevTickSecond; int status; ushort channel; NETIO_STRUCT *ioptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE (WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetIoBegin()"); status = sys$assign (&TcpIpDeviceDsc, &channel, 0, 0); if (VMSnok (status)) { /* manage channel assignment failure */ if (HttpdTickSecond < ExitTickSecond) { if (HttpdTickSecond != PrevTickSecond) { ErrorNoticed (NULL, status, NULL, FI_LI); PrevTickSecond = HttpdTickSecond; if (!ExitTickSecond) ExitTickSecond = HttpdTickSecond + NET_ASSIGN_FAIL_MAX; } /* hmmm, probably BYTLM exhausted */ if (status == SS$_EXQUOTA) return (NULL); /* shouldn't have exhausted these, but seem to have */ if (status == SS$_NOIOCHAN) return (NULL); /* some other (serious) error */ } ErrorExitVmsStatus (status, "sys$assign()", FI_LI); } ExitTickSecond = PrevTickSecond = 0; ioptr = VmGet (sizeof(NETIO_STRUCT)); ioptr->Channel = channel; return (ioptr); } /*****************************************************************************/ /* Proxy NETIO does not need the client data structure used by request processing and so conserve a little memory by not allocating that (bit shonky I know). This is noted in NET.H as well. */ NETIO_STRUCT* NetIoProxyBegin () { NETIO_STRUCT *ioptr; /*********/ /* begin */ /*********/ if (WATCH_MODULE (WATCH_MOD_NET)) WatchThis (WATCHALL, WATCH_MOD_NET, "NetIoProxyBegin()"); ioptr = VmGet (sizeof(NETIO_STRUCT) - sizeof(CLIENT_STRUCT)); return (ioptr); } /*****************************************************************************/ /* Deassign the channel as necessary and free the network I/O structure. If a TLS/SSL then do the equivalent. Return a NULL pointer. */ NETIO_STRUCT* NetIoEnd (NETIO_STRUCT *ioptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoEnd()"); if (ioptr->SesolaPtr) SesolaNetEnd (ioptr->SesolaPtr); else { if (ioptr->Channel) sys$dassgn (ioptr->Channel); VmFree (ioptr, FI_LI); } return (NULL); } /*****************************************************************************/ /* Write 'DataLength' bytes located at 'DataPtr' to the client either using either the "raw" network, or via HTTP/2, or via the Secure Sockets Layer. If 'AstFunction' zero then use sys$qiow(), waiting for completion. If an AST completion address is supplied then use sys$qio(). If empty data buffer is supplied (zero length) then declare an AST to service any AST routine supplied. If none then just return. Explicitly declares any AST routine if an error occurs. The calling function must not do any error recovery if an AST routine has been supplied but the associated AST routine must! If an AST was not supplied then the return status can be checked. */ int NetIoWrite ( NETIO_STRUCT *ioptr, VOID_AST AstFunction, void *AstParam, void *DataPtr, uint DataLength ) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoWrite() !&X !&A !&X !UL", ioptr, AstFunction, DataPtr, DataLength); if (DataPtr) { /* first call */ if (!ioptr->Http2StreamPtr || AstFunction) { /* not HTTP/2 or non-blocking */ if (ioptr->WriteAstFunction) { char buf [64]; FaoToBuffer (buf, sizeof(buf), NULL, "!&A", ioptr->WriteAstFunction); ErrorExitVmsStatus (SS$_BUGCHECK, buf, FI_LI); } ioptr->WriteCount = 0; ioptr->WriteStatus = 0; ioptr->WriteAstFunction = AstFunction; ioptr->WriteAstParam = AstParam; ioptr->WritePtr = DataPtr; ioptr->WriteLength = DataLength; ioptr->WriteIOsb.Count = 0; ioptr->WriteIOsb.Status = 0; } } if (ioptr->Http2StreamPtr) { /**********/ /* HTTP/2 */ /**********/ status = Http2NetIoWrite (ioptr, AstFunction, AstParam, DataPtr, DataLength); return (status); } if (ioptr->SesolaPtr) { /*****************/ /* secure socket */ /*****************/ status = SesolaNetIoWrite (ioptr, DataPtr, DataLength); return (status); } if (ioptr->VmsStatus) { /*********************************/ /* deliver explicitly set status */ /*********************************/ if (ioptr->WriteAstFunction) SysDclAst (NetIoWriteAst, ioptr); else NetIoWriteAst (ioptr); return (ioptr->VmsStatus); } if (WATCHING (ioptr, WATCH_NETWORK_OCTETS)) { int dlen = ioptr->WriteLength - ioptr->WriteCount; if (dlen > 65535) dlen = 65535; WatchThis (WATCHITM(ioptr), WATCH_NETWORK, "WRITE !UL (!UL/!UL) bytes (!&?non-blocking\rblocking\r)", dlen, ioptr->WriteCount, ioptr->WriteLength, ioptr->WriteAstFunction); WatchDataDump ((uchar*)ioptr->WritePtr + ioptr->WriteCount, dlen); } if (!ioptr->WriteLength) status = ioptr->WriteStatus = SS$_BUGCHECK; else if (ioptr->WriteAstFunction) { /*******************/ /* non-blocking IO */ /*******************/ /* limit size of QIO */ DataLength = ioptr->WriteLength - ioptr->WriteCount; if (DataLength > 65535) DataLength = 65535; status = sys$qio (EfnNoWait, ioptr->Channel, IO$_WRITEVBLK, &ioptr->WriteIOsb, &NetIoWriteAst, ioptr, (uchar*)ioptr->WritePtr + ioptr->WriteCount, DataLength, 0, 0, 0, 0); if (VMSnok (status)) ioptr->WriteStatus = status; } else { /***************/ /* blocking IO */ /***************/ while (ioptr->WriteCount < ioptr->WriteLength) { /* limit size of QIO */ DataLength = ioptr->WriteLength - ioptr->WriteCount; if (DataLength > 65535) DataLength = 65535; status = sys$qiow (EfnWait, ioptr->Channel, IO$_WRITEVBLK, &ioptr->WriteIOsb, 0, 0, (uchar*)ioptr->WritePtr + ioptr->WriteCount, DataLength, 0, 0, 0, 0); if (VMSnok (status)) ioptr->WriteStatus = status; else NetIoWriteAst (ioptr); if (VMSnok (ioptr->WriteStatus)) break; } } /****************/ /* check status */ /****************/ if (VMSok (status)) return (status); /* if resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* write failed, call AST explicitly, status in the IOsb */ ioptr->WriteIOsb.Status = status; ioptr->WriteIOsb.Count = 0; if (ioptr->WriteAstFunction) SysDclAst (NetIoWriteAst, ioptr); else NetIoWriteAst (ioptr); return (status); } /*****************************************************************************/ /* AST from NetIoWrite(). Call the AST function. */ void NetIoWriteAst (NETIO_STRUCT *ioptr) { void *AstParam; VOID_AST AstFunction; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoWriteAst() !&F !&X !&X !&S !UL !UL !UL", &NetIoWriteAst, ioptr, ioptr->VmsStatus, ioptr->WriteIOsb.Status, ioptr->WriteIOsb.Count, ioptr->WriteLength, ioptr->WriteCount+ioptr->WriteIOsb.Count); if (ioptr->VmsStatus) { /* explicitly set status */ ioptr->WriteIOsb.Status = ioptr->VmsStatus; ioptr->WriteIOsb.Count = 0; } ioptr->WriteStatus = ioptr->WriteIOsb.Status; if (WATCHPNT(ioptr)) { if (WATCH_CATEGORY(WATCH_NETWORK)) WatchThis (WATCHITM(ioptr), WATCH_NETWORK, "WRITE !&S !UL (!UL/!UL) bytes (!&?non-blocking\rblocking\r)", ioptr->WriteIOsb.Status, ioptr->WriteIOsb.Count, ioptr->WriteCount + ioptr->WriteIOsb.Count, ioptr->WriteLength, ioptr->WriteAstFunction); if (WATCH_CATEGORY(WATCH_RESPONSE)) if (VMSnok (ioptr->WriteIOsb.Status)) WatchThis (WATCHITM(ioptr), WATCH_RESPONSE, "NETWORK !&S (!&?non-blocking\rblocking\r)", ioptr->WriteIOsb.Status, ioptr->WriteAstFunction); } if (VMSok (ioptr->WriteIOsb.Status)) { ADD_LONG_QUAD (1, ioptr->BlocksRawTx); ADD_LONG_QUAD (1, ioptr->BlocksTallyTx); ADD_LONG_QUAD (ioptr->WriteIOsb.Count, ioptr->BytesRawTx) ADD_LONG_QUAD (ioptr->WriteIOsb.Count, ioptr->BytesTallyTx) ioptr->WriteCount += ioptr->WriteIOsb.Count; if (ioptr->WriteAstFunction) { if (ioptr->WriteCount < ioptr->WriteLength) { /* continue to write */ NetIoWrite (ioptr, NULL, NULL, NULL, 0); return; } } } else { ioptr->WriteErrorCount++; /* just note the first error status */ if (!ioptr->WriteErrorStatus) ioptr->WriteErrorStatus = ioptr->WriteIOsb.Status; if (ioptr->WriteIOsb.Status && !(ioptr->WriteIOsb.Status == SS$_ABORT || ioptr->WriteIOsb.Status == SS$_CANCEL || ioptr->WriteIOsb.Status == SS$_CONNECFAIL || ioptr->WriteIOsb.Status == SS$_IVCHAN || ioptr->WriteIOsb.Status == SS$_LINKDISCON || ioptr->WriteIOsb.Status == SS$_TIMEOUT || ioptr->WriteIOsb.Status == SS$_UNREACHABLE)) ErrorNoticed (NULL, ioptr->WriteIOsb.Status, NULL, FI_LI); } if (AstFunction = ioptr->WriteAstFunction) { AstParam = ioptr->WriteAstParam; ioptr->WriteAstFunction = ioptr->WriteAstParam = NULL; AstFunction (AstParam); } } /*****************************************************************************/ /* Deliver the supplied VMS status code via AST while maintaining the network write in-progress conditions. As with other I/O the delivery needs to be decoupled in case it gets called again during that delivery. */ void NetIoWriteStatus ( NETIO_STRUCT *ioptr, VOID_AST AstFunction, void *AstParam, int AstStatus ) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoWriteStatus() !&X !&A !&S", ioptr, AstFunction, AstStatus); if (ioptr->WriteAstFunction) { char buf [256]; FaoToBuffer (buf, sizeof(buf), NULL, "!&A", ioptr->WriteAstFunction); ErrorExitVmsStatus (SS$_BUGCHECK, buf, FI_LI); } ioptr->WriteAstFunction = AstFunction; ioptr->WriteAstParam = AstParam; ioptr->WritePtr = NULL; ioptr->WriteLength = 0; ioptr->WriteCount = ioptr->WriteIOsb.Count = 0; ioptr->WriteStatus = ioptr->WriteIOsb.Status = AstStatus; if (AstFunction) SysDclAst (NetIoWriteAst, ioptr); else NetIoWriteAst (ioptr); } /*****************************************************************************/ /* Queue up a read from the client over the network. If 'AstFunction' is zero then no I/O completion AST routine is called. If it is non-zero then the function pointed to by the parameter is called when the network write completes. Explicitly declares any AST routine if an error occurs. The calling function must not do any error recovery if an AST routine has been supplied but the associated AST routine must! If an AST was not supplied then the return status can be checked. */ int NetIoRead ( NETIO_STRUCT *ioptr, VOID_AST AstFunction, void *AstParam, void *DataPtr, uint DataSize ) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoRead() !&X !&A !&X !UL", ioptr, AstFunction, DataPtr, DataSize); if (DataPtr) { /* first call */ if (ioptr->ReadAstFunction) { char buf [256]; FaoToBuffer (buf, sizeof(buf), NULL, "!&A", ioptr->ReadAstFunction); ErrorExitVmsStatus (SS$_BUGCHECK, buf, FI_LI); } ioptr->ReadCount = 0; ioptr->ReadStatus = 0; ioptr->ReadAstFunction = AstFunction; ioptr->ReadAstParam = AstParam; ioptr->ReadPtr = DataPtr; ioptr->ReadSize = DataSize; ioptr->ReadIOsb.Count = 0; ioptr->ReadIOsb.Status = 0; } if (ioptr->Http2StreamPtr) { /**********/ /* HTTP/2 */ /**********/ status = Http2NetIoRead (ioptr); return (status); } if (ioptr->SesolaPtr) { /*****************/ /* secure socket */ /*****************/ status = SesolaNetIoRead (ioptr, DataPtr, DataSize); return (status); } if (WATCHING (ioptr, WATCH_NETWORK)) WatchThis (WATCHITM(ioptr), WATCH_NETWORK, "!AZ !UL/!UL bytes (!&?non-blocking\rblocking\r)", ioptr->ReadSize & NETIO_DATA_FILL_BUF ? "FILL" : "READ", ioptr->ReadCount, ioptr->ReadSize & ~NETIO_DATA_FILL_BUF, ioptr->ReadAstFunction); if (ioptr->VmsStatus) { /*********************************/ /* deliver explicitly set status */ /*********************************/ if (ioptr->ReadAstFunction) SysDclAst (NetIoReadAst, ioptr); else NetIoReadAst (ioptr); return (ioptr->VmsStatus); } if (!ioptr->ReadSize) status = ioptr->ReadStatus = SS$_BUGCHECK; else if (ioptr->ReadAstFunction) { /*******************/ /* non-blocking IO */ /*******************/ DataSize = ioptr->ReadSize & ~NETIO_DATA_FILL_BUF; DataSize -= ioptr->ReadCount; if (DataSize > 65535) DataSize = 65535; status = sys$qio (EfnNoWait, ioptr->Channel, IO$_READVBLK, &ioptr->ReadIOsb, &NetIoReadAst, ioptr, (uchar*)ioptr->ReadPtr+ioptr->ReadCount, DataSize, 0, 0, 0, 0); if (VMSnok (status)) ioptr->ReadStatus = status; } else { /***************/ /* blocking IO */ /***************/ for (;;) { DataSize = ioptr->ReadSize & ~NETIO_DATA_FILL_BUF; DataSize -= ioptr->ReadCount; if (DataSize > 65535) DataSize = 65535; else if (!DataSize) break; status = sys$qiow (EfnWait, ioptr->Channel, IO$_READVBLK, &ioptr->ReadIOsb, 0, 0, (uchar*)ioptr->ReadPtr+ioptr->ReadCount, DataSize, 0, 0, 0, 0); if (VMSnok (status)) ioptr->ReadStatus = status; else NetIoReadAst (ioptr); if (VMSnok (ioptr->ReadStatus)) break; } } /****************/ /* check status */ /****************/ /* if I/O successful */ if (VMSok (status)) return (status); /* with resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* write failed, call AST explicitly, status in the IOsb */ ioptr->ReadIOsb.Status = status; ioptr->ReadIOsb.Count = 0; if (ioptr->ReadAstFunction) SysDclAst (NetIoReadAst, ioptr); else NetIoReadAst (ioptr); return (status); } /*****************************************************************************/ /* AST from NetIoRead(). Call the AST function. */ void NetIoReadAst (NETIO_STRUCT *ioptr) { void *AstParam; VOID_AST AstFunction; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoReadAst() !&F !&X !&X !&S !UL !UL", NetIoReadAst, ioptr, ioptr->VmsStatus, ioptr->ReadIOsb.Status, ioptr->ReadIOsb.Count, ioptr->ReadCount); if (VMSok (ioptr->ReadIOsb.Status)) { /* zero bytes with a normal status (once seen with TGV-Multinet) */ if (!ioptr->ReadIOsb.Count) ioptr->ReadIOsb.Status = SS$_ABORT; } if (ioptr->VmsStatus) { /* explicitly set status */ ioptr->ReadIOsb.Status = ioptr->VmsStatus; ioptr->ReadIOsb.Count = 0; } ioptr->ReadStatus = ioptr->ReadIOsb.Status; if (WATCHPNT(ioptr)) { if (WATCH_CATEGORY(WATCH_NETWORK) || WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) { WatchThis (WATCHITM(ioptr), WATCH_NETWORK, "!AZ !&S !UL (!UL/!UL) bytes (!&?non-blocking\rblocking\r)", ioptr->ReadSize & NETIO_DATA_FILL_BUF ? "FILL" : "READ", ioptr->ReadIOsb.Status, ioptr->ReadIOsb.Count, ioptr->ReadCount + ioptr->ReadIOsb.Count, ioptr->ReadSize & ~NETIO_DATA_FILL_BUF, ioptr->ReadAstFunction); if (WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) if (VMSok(ioptr->ReadIOsb.Status)) WatchDataDump (ioptr->ReadPtr + ioptr->ReadCount, ioptr->ReadIOsb.Count); } } if (VMSok (ioptr->ReadIOsb.Status)) { ADD_LONG_QUAD (1, ioptr->BlocksRawRx); ADD_LONG_QUAD (1, ioptr->BlocksTallyRx); ADD_LONG_QUAD (ioptr->ReadIOsb.Count, ioptr->BytesRawRx) ADD_LONG_QUAD (ioptr->ReadIOsb.Count, ioptr->BytesTallyRx) ioptr->ReadCount += ioptr->ReadIOsb.Count; if (ioptr->ReadAstFunction) { if (ioptr->ReadSize & NETIO_DATA_FILL_BUF) { if (ioptr->ReadCount < (ioptr->ReadSize & ~NETIO_DATA_FILL_BUF)) { /* read more to fill buffer */ NetIoRead (ioptr, NULL, NULL, NULL, 0); return; } } } } else { ioptr->ReadErrorCount++; /* just note the first error status */ if (!ioptr->ReadErrorStatus) ioptr->ReadErrorStatus = ioptr->ReadIOsb.Status; if (!(ioptr->ReadIOsb.Status == SS$_ABORT || ioptr->ReadIOsb.Status == SS$_CANCEL || ioptr->ReadIOsb.Status == SS$_CONNECFAIL || ioptr->ReadIOsb.Status == SS$_IVCHAN || ioptr->ReadIOsb.Status == SS$_LINKDISCON || ioptr->ReadIOsb.Status == SS$_TIMEOUT || ioptr->ReadIOsb.Status == SS$_UNREACHABLE)) ErrorNoticed (NULL, ioptr->ReadIOsb.Status, NULL, FI_LI); } if (AstFunction = ioptr->ReadAstFunction) { AstParam = ioptr->ReadAstParam; ioptr->ReadAstFunction = ioptr->ReadAstParam = NULL; AstFunction (AstParam); } } /*****************************************************************************/ /* Deliver the supplied VMS status code via AST while maintaining the network write in-progress conditions. As with other I/O the delivery needs to be decoupled in case it gets called again during that delivery. Primarily used to indicate SS$_CANCEL in HTTP/2 processing. */ void NetIoReadStatus ( NETIO_STRUCT *ioptr, VOID_AST AstFunction, void *AstParam, int AstStatus ) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoReadStatus() !&X !&A !&S", ioptr, AstFunction, AstStatus); if (ioptr->ReadAstFunction) { char buf [256]; FaoToBuffer (buf, sizeof(buf), NULL, "!&A", ioptr->ReadAstFunction); ErrorExitVmsStatus (SS$_BUGCHECK, buf, FI_LI); } ioptr->ReadAstFunction = AstFunction; ioptr->ReadAstParam = AstParam; ioptr->ReadPtr = NULL; ioptr->ReadSize = 0; ioptr->ReadCount = ioptr->ReadIOsb.Count = 0; ioptr->ReadStatus = ioptr->ReadIOsb.Status = AstStatus; if (AstFunction) SysDclAst (NetIoReadAst, ioptr); else NetIoReadAst (ioptr); } /*****************************************************************************/ /* Read without consuming (raw) data from the (raw) socket. */ int NetIoPeek ( REQUEST_STRUCT *rqptr, REQUEST_AST AstFunction ) { int status; NETIO_STRUCT *ioptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_NET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetIoPeek() !&A", AstFunction); if (WATCHING (rqptr, WATCH_NETWORK_OCTETS)) WatchThis (WATCHITM(rqptr), WATCH_NETWORK, "PEEK (!&?non-blocking\rblocking\r)", AstFunction); ioptr = rqptr->NetIoPtr; if (AstFunction) { ioptr->PeekAstParam = rqptr; ioptr->PeekAstFunction = AstFunction; } if (ioptr->VmsStatus) { /*********************************/ /* deliver explicitly set status */ /*********************************/ if (ioptr->PeekAstFunction) SysDclAst (NetIoPeekAst, ioptr); else NetIoPeekAst (ioptr); return (ioptr->VmsStatus); } if (AstFunction) { /*******************/ /* non-blocking IO */ /*******************/ status = sys$qio (EfnNoWait, ioptr->Channel, IO$_READVBLK, &ioptr->PeekIOsb, NetIoPeekAst, ioptr, ioptr->PeekBuffer, sizeof(ioptr->PeekBuffer), 0, TCPIP$C_MSG_PEEK, 0, 0); } else { /***************/ /* blocking IO */ /***************/ status = sys$qiow (EfnWait, ioptr->Channel, IO$_READVBLK, &ioptr->PeekIOsb, 0, 0, ioptr->PeekBuffer, sizeof(ioptr->PeekBuffer), 0, TCPIP$C_MSG_PEEK, 0, 0); if (VMSok (status)) status = ioptr->PeekIOsb.Status; } /****************/ /* check status */ /****************/ /* if I/O successful */ if (VMSok (status)) return (status); /* with resource wait enabled the only quota not waited for is ASTLM */ if (status == SS$_EXQUOTA) { /* no ASTs means not much of anything else can happen so just exit! */ sys$canexh(0); /* make the message a little more meaningful */ sys$exit (SS$_EXASTLM); } /* IVCHAN sometimes occurs if the socket has been closed (ignore) */ if (status != SS$_IVCHAN) ErrorNoticed (rqptr, status, "sys$qio", FI_LI); /* queuing of peek failed, call AST explicitly, status in the IOsb */ ioptr->PeekIOsb.Count = 0; ioptr->PeekIOsb.Status = status; if (ioptr->PeekAstFunction) SysDclAst (NetIoPeekAst, ioptr); else NetIoPeekAst (ioptr); return (status); } /*****************************************************************************/ /* AST from NetIoPeek(). Call the AST function. */ void NetIoPeekAst (NETIO_STRUCT *ioptr) { void *AstParam; VOID_AST AstFunction; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoPeekAst() !&F !&X !&X !&S !UL !UL", NetIoPeekAst, ioptr, ioptr->VmsStatus, ioptr->PeekIOsb.Status, ioptr->PeekIOsb.Count); if (ioptr->VmsStatus) { /* explicitly set status */ ioptr->PeekIOsb.Status = ioptr->VmsStatus; ioptr->PeekIOsb.Count = 0; } if (WATCHPNT(ioptr)) { if (WATCH_CATEGORY(WATCH_NETWORK) || WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) { WatchThis (WATCHITM(ioptr), WATCH_NETWORK, "PEEK !&S !UL bytes (!&?non-blocking\rblocking\r)", ioptr->PeekIOsb.Status, ioptr->PeekIOsb.Count, ioptr->PeekAstFunction); if (WATCH_CATEGORY(WATCH_NETWORK_OCTETS)) if (VMSok(ioptr->PeekIOsb.Status)) WatchDataDump (ioptr->PeekBuffer, ioptr->PeekIOsb.Count); } } AstParam = ioptr->PeekAstParam; AstFunction = ioptr->PeekAstFunction; ioptr->PeekAstFunction = ioptr->PeekAstParam = NULL; if (AstFunction) AstFunction (AstParam); } /****************************************************************************/ /* Any $QIO I/O currently outstanding? */ BOOL NetIoInProgress (NETIO_STRUCT *ioptr) { int channel, status; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoInProgress()"); if (ioptr->Http2StreamPtr) return (Http2NetIoInProgress (ioptr->Http2StreamPtr)); if (ioptr->SesolaPtr) return (SesolaNetIoInProgress (ioptr->SesolaPtr)); return (ioptr->WriteAstFunction || ioptr->ReadAstFunction || ioptr->PeekAstFunction); } /****************************************************************************/ /* Cancel network I/O in-progress. */ void NetIoCancel (NETIO_STRUCT *ioptr) { /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoCancel()"); ioptr->VmsStatus = SS$_CANCEL; if (ioptr->Http2StreamPtr) Http2RequestCancel (((HTTP2_STREAM_STRUCT*)ioptr->Http2StreamPtr)->RequestPtr); else if (ioptr->SesolaPtr) SesolaNetIoCancel (ioptr); else if (ioptr->WriteAstFunction || ioptr->ReadAstFunction || ioptr->PeekAstFunction) sys$cancel (ioptr->Channel); } /****************************************************************************/ /* Just close the socket, bang! */ int NetIoCloseSocket (NETIO_STRUCT *ioptr) { int channel, status; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoCloseSocket()"); /* this is request-oriented so HTTP/2 connections are handled separately */ if (ioptr->Http2StreamPtr) return (SS$_NORMAL); if (!ioptr->Channel) return (SS$_NORMAL); status = sys$dassgn (channel = ioptr->Channel); ioptr->Channel = 0; if (WATCH_CATEGORY(WATCH_CONNECT)) WatchThis (WATCHITM(ioptr), WATCH_CONNECT, "CLOSE channel !UL !&S", channel, status); return (status); } /*****************************************************************************/ /* Allows *testing* of NetIoWrite() for small, medium, large and huge buffers. A request to UTI "/$/NetIoWriteTest/?" will invoke this function. Writes responses of ASCII in quantities from 1 byte to whatever. Non-blocking by default, negative quantity to make blocking. */ #if WATCH_MOD void NetIoWriteTest (REQUEST_STRUCT *rqptr) { int ch, nonblock, size; char *cptr, *sptr, *zptr; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_NET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetIoWriteTest()"); if (rqptr->NotePadPtr != NetIoWriteTest) { rqptr->NotePadPtr = NetIoWriteTest; if (!(cptr = rqptr->rqHeader.QueryStringPtr)) cptr = "32767"; size = atoi(cptr); if (size >= 0) nonblock = 1; else nonblock = 0; size = abs(size); cptr = VmGetHeap (rqptr, size+32); zptr = (sptr = cptr) + size; ch = '!'; while (sptr < zptr) { while (ch <= '~' && sptr < zptr) *sptr++ = ch++; if (sptr < zptr) *sptr++ = '\n'; ch = '!'; } rqptr->rqResponse.NoGzip = true; ResponseHeader (rqptr, 200, "text/plain", size, NULL, NULL); if (nonblock) { NetWrite (rqptr, NetIoWriteTest, cptr, size); return; } NetWrite (rqptr, NULL, cptr, size); } RequestEnd2 (rqptr); } #endif /* WATCH_MOD */ /*****************************************************************************/ /* Allows *testing* of NetIoRead() for small, medium, large and huge buffers. Using cURL and a POSTed file to buffer. Non-blocking by default, negative quantity to make blocking. $ curl --insecure "-XPOST" --data-binary @ - "http[s]:///$/NetIoReadTest/?" */ #if WATCH_MOD void NetIoReadTest (REQUEST_STRUCT *rqptr) { int nonblock = 1, size; ushort slen; char *cptr; char buf [256]; /*********/ /* begin */ /*********/ if (WATCHMOD (rqptr, WATCH_MOD_NET)) WatchThis (WATCHITM(rqptr), WATCH_MOD_NET, "NetIoReadTest()"); if (rqptr->NotePadPtr != NetIoWriteTest) { rqptr->NotePadPtr = NetIoWriteTest; if (atoi(rqptr->rqHeader.QueryStringPtr) < 0) nonblock = 0; size = rqptr->rqHeader.ContentLength; cptr = VmGetHeap (rqptr, size+32); size |= NETIO_DATA_FILL_BUF; if (nonblock) { NetRead (rqptr, &NetIoReadTest, cptr, size); return; } NetRead (rqptr, NULL, cptr, size); } ADD_LONG_QUAD (rqptr->NetIoPtr->ReadCount, rqptr->NetIoPtr->BytesRawRx); ADD_LONG_QUAD (rqptr->NetIoPtr->ReadCount, rqptr->NetIoPtr->BytesTallyRx); FaoToBuffer (buf, sizeof(buf), &slen, "status:!&S count:!UL\n", rqptr->NetIoPtr->ReadStatus, rqptr->NetIoPtr->ReadCount); rqptr->rqResponse.NoGzip = true; ResponseHeader (rqptr, 200, "text/plain", slen, NULL, NULL); NetWrite (rqptr, NULL, buf, slen); RequestEnd2 (rqptr); } #endif /* WATCH_MOD */ /*****************************************************************************/