/*****************************************************************************/ /* 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 --------------- 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" /******************/ /* 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; ioptr->QioMaxSeg = NETIO_QIO_BYTES_SENTINAL; 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)); ioptr->QioMaxSeg = NETIO_QIO_BYTES_SENTINAL; 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); } } /*****************************************************************************/ /* QIOing with a maximum of the TCP MSS seems so much more efficient (at least for VSI AXPVMS VMS V8.4-2L1 and VSI AXPVMS TCPIP V5.7). The driver must have to do a lot less work if the QIO data length is no greater than the TCP MSS. Defaults to the MSS of ethernet (1460). System logical name WASD_QIO_MAXSEG will allow this value to be explictly set. There is no sanity check on this value. Logical name is tested once at first request (i.e. at server startup). */ void NetIoQioMaxSeg (NETIO_STRUCT *ioptr) { static int QioMaxSeg = -1; char *cptr; /*********/ /* begin */ /*********/ if (WATCHMOD (ioptr, WATCH_MOD_NET)) WatchThis (WATCHITM(ioptr), WATCH_MOD_NET, "NetIoQioMaxSeg()"); if (QioMaxSeg < 0) if (cptr = SysTrnLnm ("WASD_QIO_MAXSEG")) QioMaxSeg = atoi (cptr); else QioMaxSeg = 0; TcpIpSocketBufferSize (ioptr); if (QioMaxSeg) ioptr->QioMaxSeg = QioMaxSeg; else if (ioptr->TcpIpTcpMaxSeg) { ioptr->QioMaxSeg = ioptr->TcpIpTcpMaxSeg; if (ioptr->QioMaxSeg > NETIO_QIO_BYTES_MAX) ioptr->QioMaxSeg = NETIO_QIO_BYTES_MAX; else if (ioptr->QioMaxSeg < NETIO_QIO_BYTES_MIN) ioptr->QioMaxSeg = NETIO_QIO_BYTES_MIN; } else ioptr->QioMaxSeg = NETIO_QIO_BYTES_SENTINAL; if (ioptr->QioMaxSeg != ioptr->TcpIpTcpMaxSeg) if (WATCHING (ioptr, WATCH_NETWORK)) WatchThis (WATCHITM(ioptr), WATCH_NETWORK, "QIO maxseg:!UL", ioptr->QioMaxSeg); } /*****************************************************************************/ /* 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) { HttpdStackTrace (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->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 > ioptr->QioMaxSeg) dlen = ioptr->QioMaxSeg; 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 > ioptr->QioMaxSeg) DataLength = ioptr->QioMaxSeg; 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) DataLength = NETIO_QIO_BYTES_SENTINAL; 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)) { 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 || 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 (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->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 > ioptr->QioMaxSeg) DataSize = ioptr->QioMaxSeg; 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->QioMaxSeg) DataSize = ioptr->QioMaxSeg; 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)) { 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 || 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->Http2StreamPtr) return (Http2NetIoInProgress (ioptr->Http2StreamPtr)); 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->Http2StreamPtr) Http2RequestCancel (((HTTP2_STREAM_STRUCT*)ioptr->Http2StreamPtr)->RequestPtr); 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->Http2StreamPtr) 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 */ /*****************************************************************************/