/*****************************************************************************/ /* 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|. $QIO WRITE BUFFER ----------------- Using logical name WASD_QIO_MAXSEG as described in NetIoQioMaxSeg() maximising the $QIO write buffer definitely results the most efficient transfer of network data. Note the MONITOR MODES data for interupt, kernel, user and idle percentages. Roughly half the higher modes for the same USER mode. $QIO at MSS * ((65535 / MSS) - 1) i.e. for a MSS of 1460 would use 62780 where a maximum buffer would write 43 full MSS datagrams. The 62780 also corresponds to the mysterious "Socket buffer bytes" and "Socket buffer quota" values. $ tcpip show device /full bg29146 Device_socket: bg29146 Type: STREAM RECEIVE SEND Queued I/O 0 0 Q0LEN 0 Socket buffer bytes 0 62780 QLEN 0 Socket buffer quota 62780 62780 8< snip 8< The test setup and monitor results: $ curl -vo /dev/null http://n.n.n.n/stream/octet/ HP TCP/IP Services for OpenVMS Alpha Version V5.7 - ECO 5 on a Digital Personal WorkStation running OpenVMS V8.4-2L1 $QIO at MSS $ curl -vo /dev/null http://192.168.1.3/stream/octet/ Interrupt State 39 |*************** Kernel Mode 15 |****** User Mode 20 |******** Idle Time 26 |********** $QIO at MSS $ curl -vo /dev/null http://192.168.1.3/stream/qio/ Interrupt State 32 |************ Kernel Mode 49 |******************* User Mode 6 |** Idle Time 12 |**** $QIO at (65535 / MSS) * MSS $ curl -vo /dev/null http://192.168.1.3/stream/octet/ Interrupt State 17 |****** Kernel Mode 4 |* User Mode 17 |****** Idle Time 62 |************************ $QIO at (65535 / MSS) * MSS $ curl -vo /dev/null http://192.168.1.3/stream/qio/ Interrupt State 19 |******* Kernel Mode 9 |*** Executive Mode 1 | User Mode 1 | Idle Time 71 |**************************** VSI TCP/IP Services for OpenVMS x86_64 Version X6.0 on an innotek GmbH VirtualBox running OpenVMS V9.1-A (2 x CPU) $QIO at MSS $ curl -vo /dev/null http://192.168.1.86/stream/octet/ Interrupt State 58 |*********** MP Synchronization 3 | Kernel Mode 19 |*** User Mode 8 |* Idle Time 112 |********************** $QIO at MSS $ curl -vo /dev/null http://192.168.1.86/stream/qio/ Interrupt State 80 |**************** MP Synchronization 1 | Kernel Mode 50 |********** User Mode 1 | Idle Time 68 |************* $QIO at (65535 / MSS) * MSS $ curl -vo /dev/null http://192.168.1.86/stream/octet/ Interrupt State 33 |****** Kernel Mode 1 | User Mode 7 |* Idle Time 159 |******************************* $QIO at (65535 / MSS) * MSS $ curl -vo /dev/null http://192.168.1.86/stream/qio/ Interrupt State 32 |****** Kernel Mode 4 | Idle Time 164 |******************************** There was also a corresponding increase in data transfer rate (MB/s). VERSION HISTORY --------------- 15-JUL-2020 MGD bugfix; NetIoWriteStatus() and NetIoReadStatus() 18-APR-2020 MGD NetIoQioMaxSeg() tune QIO to TCP MSS 24-DEC-2019 MGD NetIoWriteStatus() and NetIoReadStatus() count parameter 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" /* calculate $QIO buffer size */ #define NETIO_QIO_SIZE(mss) (mss * ((65535 / mss) - 1)) /******************/ /* 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; char *cptr; 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. */ void 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 { sys$dassgn (ioptr->Channel); VmFree (ioptr, FI_LI); } } /*****************************************************************************/ /* 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->Stream2Ptr || AstFunction) { /* not HTTP/2 or non-blocking */ if (ioptr->WriteAstFunction) { HttpdStackTrace ("NetIoWrite()", FI_LI); status = SS$_BUGCHECK; ErrorNoticed (ioptr->RequestPtr, status, "!&A", FI_LI, ioptr->WriteAstFunction); return (status); } 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->Stream2Ptr) { /**********/ /* 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 > ioptr->TcpMaxQio) dlen = ioptr->TcpMaxQio; 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 (Watch.TriggerTxCount) { int dlen = ioptr->WriteLength - ioptr->WriteCount; if (dlen > ioptr->TcpMaxQio) dlen = ioptr->TcpMaxQio; if (!ioptr->WatchTriggerTxPlus) { if (WatchTrigger ((uchar*)ioptr->WritePtr + ioptr->WriteCount, dlen, Watch.TriggerTx)) { ioptr->WatchTriggerTx = true; if (Watch.TriggerPlus) ioptr->WatchTriggerTxPlus = true; } } if (ioptr->WatchTriggerTx || ioptr->WatchTriggerTxPlus) { WatchSetTrigger (ioptr); ioptr->WatchTriggerTx = false; } } 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 > ioptr->TcpMaxQio) DataLength = ioptr->TcpMaxQio; 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 > ioptr->TcpMaxQio) DataLength = ioptr->TcpMaxQio; 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 !&A", &NetIoWriteAst, ioptr, ioptr->VmsStatus, ioptr->WriteIOsb.Status, ioptr->WriteIOsb.Count, ioptr->WriteLength, ioptr->WriteCount+ioptr->WriteIOsb.Count, ioptr->WriteAstFunction); if (ioptr->VmsStatus) { /* explicitly set status */ ioptr->WriteIOsb.Status = ioptr->VmsStatus; ioptr->WriteIOsb.Count = 0; } ioptr->WriteStatus = ioptr->WriteIOsb.Status; if (ioptr->WriteIOsb.Status == SS$_NORMAL && !(ioptr->WriteCount || ioptr->WriteLength)) { /* special case, do not tally or report via WATCH */ if (AstFunction = ioptr->WriteAstFunction) { AstParam = ioptr->WriteAstParam; ioptr->WriteAstFunction = ioptr->WriteAstParam = NULL; AstFunction (AstParam); } return; } if (WATCHPNT(ioptr)) { /* the SS$_WASECC is returned when GZIPing network data */ if (ioptr->WriteIOsb.Status != SS$_WASECC) { 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)) { ioptr->BlocksRawTx64++; ioptr->BlocksTallyTx64++; ioptr->BytesRawTx64 += ioptr->WriteIOsb.Count; ioptr->BytesTallyTx64 += ioptr->WriteIOsb.Count; 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 || ioptr->WriteIOsb.Status == SS$_VCCLOSED)) { char *sptr = "(none)"; REQUEST_STRUCT *rqptr; if (rqptr = ioptr->RequestPtr) if (rqptr->rqHeader.RequestUriPtr) sptr = rqptr->rqHeader.RequestUriPtr; ErrorNoticed (NULL, ioptr->WriteIOsb.Status, "!AZ", FI_LI, sptr); } } 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. */ int NetIoWriteStatus ( NETIO_STRUCT *ioptr, VOID_AST AstFunction, void *AstParam, int AstStatus, int WriteCount ) { 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; if (!WriteCount) ioptr->WritePtr = NULL; ioptr->WriteCount = 0; ioptr->WriteLength = WriteCount; ioptr->WriteIOsb.Count = WriteCount; ioptr->WriteIOsb.Status = AstStatus; if (AstFunction) SysDclAst (NetIoWriteAst, ioptr); else NetIoWriteAst (ioptr); return (AstStatus); } /*****************************************************************************/ /* 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]; HttpdStackTrace ("NetIoRead()", FI_LI); FaoToBuffer (buf, sizeof(buf), NULL, "!&A", ioptr->ReadAstFunction); status = SS$_BUGCHECK; ErrorNoticed (ioptr->RequestPtr, status, buf, FI_LI); return (status); } 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->Stream2Ptr) { /**********/ /* 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 > ioptr->TcpMaxQio) DataSize = ioptr->TcpMaxQio; 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 > ioptr->TcpMaxQio) DataSize = ioptr->TcpMaxQio; 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 !&A", NetIoReadAst, ioptr, ioptr->VmsStatus, ioptr->ReadIOsb.Status, ioptr->ReadIOsb.Count, ioptr->ReadCount, ioptr->ReadAstFunction); 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 (ioptr->ReadIOsb.Status == SS$_NORMAL && !(ioptr->ReadCount || ioptr->ReadSize)) { /* special case, do not tally or report via WATCH */ if (AstFunction = ioptr->ReadAstFunction) { AstParam = ioptr->ReadAstParam; ioptr->ReadAstFunction = ioptr->ReadAstParam = NULL; AstFunction (AstParam); } return; } 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)) { ioptr->BlocksRawRx64++; ioptr->BlocksTallyRx64++; ioptr->BytesRawRx64 += ioptr->ReadIOsb.Count; ioptr->BytesTallyRx64 += ioptr->ReadIOsb.Count; 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; } } } if (Watch.TriggerRxCount) { if (!ioptr->WatchTriggerRxPlus) { if (WatchTrigger (ioptr->ReadPtr, ioptr->ReadCount, Watch.TriggerRx)) { ioptr->WatchTriggerRx = true; if (Watch.TriggerPlus) ioptr->WatchTriggerRxPlus = true; } } if (ioptr->WatchTriggerRx || ioptr->WatchTriggerRxPlus) { WatchSetTrigger (ioptr); ioptr->WatchTriggerRx = false; } } } 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 || ioptr->ReadIOsb.Status == SS$_VCCLOSED)) { char *sptr = "(none)"; REQUEST_STRUCT *rqptr; if (rqptr = ioptr->RequestPtr) if (rqptr->rqHeader.RequestUriPtr) sptr = rqptr->rqHeader.RequestUriPtr; ErrorNoticed (NULL, ioptr->ReadIOsb.Status, "!AZ", FI_LI, sptr); } } 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, uint ReadCount ) { int status; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoReadStatus() !&X !UL !&A !&S", ioptr, ReadCount, 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; if (!ReadCount) { ioptr->ReadPtr = NULL; ioptr->ReadSize = 0; } ioptr->ReadCount = 0; ioptr->ReadIOsb.Count = ReadCount; ioptr->ReadIOsb.Status = AstStatus; if (AstFunction) SysDclAst (NetIoReadAst, ioptr); else NetIoReadAst (ioptr); } /****************************************************************************/ /* 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->Stream2Ptr) return (Http2NetIoInProgress (ioptr->Stream2Ptr)); if (ioptr->SesolaPtr) return (SesolaNetIoInProgress (ioptr->SesolaPtr)); return (ioptr->WriteAstFunction || ioptr->ReadAstFunction); } /****************************************************************************/ /* 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->Stream2Ptr) Http2NetIoCancel (ioptr); else if (ioptr->SesolaPtr) SesolaNetIoCancel (ioptr); else if (ioptr->WriteAstFunction || ioptr->ReadAstFunction) sys$cancel (ioptr->Channel); } /****************************************************************************/ /* Just close the socket (channel), bang! */ int NetIoCloseSocket (NETIO_STRUCT *ioptr) { int channel, status; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoCloseSocket()"); if (ioptr->Stream2Ptr) 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; int64 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.ContentLength64; cptr = VmGetHeap (rqptr, size+32); size |= NETIO_DATA_FILL_BUF; if (nonblock) { NetRead (rqptr, &NetIoReadTest, cptr, size); return; } NetRead (rqptr, NULL, cptr, size); } rqptr->NetIoPtr->BytesRawRx64 += rqptr->NetIoPtr->ReadCount; rqptr->NetIoPtr->BytesTallyRx64 += rqptr->NetIoPtr->ReadCount; 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 */ /*****************************************************************************/