SDL-Hercules-390 / hyperion

The SDL Hercules 4.x Hyperion version of the System/370, ESA/390, and z/Architecture Emulator
Other
246 stars 92 forks source link

Request for TCP/IP (sockdev) functionality be added to punch devices. #553

Closed wa6our closed 1 year ago

wa6our commented 1 year ago

I have two Hercules images living on Oracle cloud. I have a program I wrote that connects a 3270 and Line printer to my local MAC via TCP/IP. I would like to do the same with the Punch. I am told that should be simple to do. I hope it is.

Thanks for a wonderful product.

MockbaTheBorg commented 1 year ago

@wa6our have you tried this? attach 002d 3525 0.0.0.0:3525 ascii (from Hercules prompt) or 002d 3525 0.0.0.0:3525 ascii (added to the hercules cfg)

This creates a socket for the punch, just like a printer. Works the same for punches, readers, printers.

Sorry if I misunderstood your request, but this is how I do it here on my "mainframe".

MockbaTheBorg commented 1 year ago

Never mind, it doesn't seem to work, even though the configuration takes without errors. it just creates a 0.0.0.0 file.

I will leave my reply above here as an ode to my stupidity.

Fish-Git commented 1 year ago

@wa6our? (what is your name by the way?)

Since you're running Hercules on a Mac, may I presume you are able to build the current development (git develop branch) version of Hercules?

I've made the needed changes (see patch below) but am scratching my head over how to test it without having to write my own punch spooler application. It would be faster and easier if you could apply the below patch to current development git and give it a try. Thanks.

--- hyperion-git/cardpch.c  2022-11-26 17:01:05.936039300 -0800
+++ hyperion-1/cardpch.c    2023-04-03 13:29:50.077871300 -0700
@@ -14,6 +14,7 @@
 #include "hercules.h"

 #include "devtype.h"
+#include "sockdev.h"

 /*-------------------------------------------------------------------*/
 /* Internal macro definitions                                        */
@@ -29,23 +30,157 @@
 {
 int             rc;                     /* Return code               */

-    /* Write data to the output file */
-    rc = write (dev->fd, buf, len);
+    if (dev->bs)    /* socket device? */
+    {
+        /* Write data to socket, check for error */
+        if ((rc = write_socket( dev->fd, buf, len )) < len)
+        {
+            /* Close the connection */
+            if (dev->fd != -1)
+            {
+                int fd = dev->fd;
+                dev->fd = -1;
+                close_socket( fd );
+                // "%1d:%04X Card: client %s, IP %s disconnected from device %s"
+                WRMSG( HHC01211, "I", LCSS_DEVNUM, dev->bs->clientname, dev->bs->clientip, dev->bs->spec );
+            }

-    /* Equipment check if error writing to output file */
-    if (rc < len)
+            /* Set unit check with intervention required */
+            dev->sense[0] = SENSE_IR;
+            *unitstat = CSW_CE | CSW_DE | CSW_UC;
+        }
+    }
+    else
     {
-        // "%1d:%04X %s: error in function %s: %s"
-        WRMSG( HHC01250, "E", LCSS_DEVNUM,
-               "Card", "write()", errno == 0 ? "incomplete"
-                                             : strerror( errno ));
-        dev->sense[0] = SENSE_EC;
-        *unitstat = CSW_CE | CSW_DE | CSW_UC;
-        return;
+        /* Write data to the output file */
+        rc = write (dev->fd, buf, len);
+
+        /* Equipment check if error writing to output file */
+        if (rc < len)
+        {
+            // "%1d:%04X %s: error in function %s: %s"
+            WRMSG( HHC01250, "E", LCSS_DEVNUM,
+                   "Card", "write()", errno == 0 ? "incomplete"
+                                                 : strerror( errno ));
+            dev->sense[0] = SENSE_EC;
+            *unitstat = CSW_CE | CSW_DE | CSW_UC;
+        }
     }

 } /* end function write_buffer */

+/*-------------------------------------------------------------------*/
+/* Thread to monitor the sockdev remote punch spooler connection     */
+/*-------------------------------------------------------------------*/
+static void* spthread (void* arg)
+{
+    DEVBLK* dev = (DEVBLK*) arg;
+    BYTE byte;
+    fd_set readset, errorset;
+    struct timeval tv;
+    int rc, fd = dev->fd;           // (save original fd)
+
+    /* Fix thread name */
+    {
+        char    thread_name[16];
+        MSGBUF( thread_name, "spthread %1d:%04X", LCSS_DEVNUM );
+        SET_THREAD_NAME( thread_name );
+    }
+
+    // Looooop...  until shutdown or disconnect...
+
+    // PROGRAMMING NOTE: we do our select specifying an immediate
+    // timeout to prevent our select from holding up (slowing down)
+    // the device thread (which does the actual writing of data to
+    // the client). The only purpose for our thread even existing
+    // is to detect a severed connection (i.e. to detect when the
+    // client disconnects)...
+
+    while ( !sysblk.shutdown && dev->fd == fd )
+    {
+        if (dev->busy)
+        {
+            SLEEP(3);
+            continue;
+        }
+
+        FD_ZERO( &readset );
+        FD_ZERO( &errorset );
+
+        FD_SET( fd, &readset );
+        FD_SET( fd, &errorset );
+
+        tv.tv_sec = 0;
+        tv.tv_usec = 0;
+
+        rc = select( fd+1, &readset, NULL, &errorset, &tv );
+
+        if (rc < 0)
+            break;
+
+        if (rc == 0)
+        {
+            SLEEP(3);
+            continue;
+        }
+
+        if (FD_ISSET( fd, &errorset ))
+            break;
+
+        // Read and ignore any data they send us...
+        // Note: recv should complete immediately
+        // as we know data is waiting to be read.
+
+        ASSERT( FD_ISSET( fd, &readset ) );
+
+        rc = recv( fd, &byte, sizeof(byte), 0 );
+
+        if (rc <= 0)
+            break;
+    }
+
+    obtain_lock( &dev->lock );
+
+    // PROGRAMMING NOTE: the following tells us whether we detected
+    // the error or if the device thread already did. If the device
+    // thread detected it while we were sleeping (and subsequently
+    // closed the connection) then we don't need to do anything at
+    // all; just exit. If we were the ones that detected the error
+    // however, then we need to close the connection so the device
+    // thread can learn of it...
+
+    if (dev->fd == fd)
+    {
+        dev->fd = -1;
+        close_socket( fd );
+        // "%1d:%04X Card: client %s, IP %s disconnected from device %s"
+        WRMSG( HHC01211, "I", LCSS_DEVNUM, dev->bs->clientname, dev->bs->clientip, dev->bs->spec );
+    }
+
+    release_lock( &dev->lock );
+
+    return NULL;
+
+} /* end function spthread */
+
+/*-------------------------------------------------------------------*/
+/* Sockdev "OnConnection" callback function                          */
+/*-------------------------------------------------------------------*/
+static int onconnect_callback (DEVBLK* dev)
+{
+    TID tid;
+    int rc;
+
+    rc = create_thread( &tid, DETACHED, spthread, dev, "spthread" );
+    if (rc)
+    {
+        // "Error in function create_thread(): %s"
+        WRMSG( HHC00102, "E", strerror( rc ) );
+        return 0;
+    }
+    return 1;
+}
+
 // (forward reference)
 static int open_punch( DEVBLK* dev );

@@ -55,6 +190,7 @@
 static int cardpch_init_handler( DEVBLK* dev, int argc, char* argv[] )
 {
 int     i;                              /* Array subscript           */
+bool    sockdev = false;

     /* Close the existing file, if any */
     if (dev->fd >= 0)
@@ -158,13 +294,52 @@
             continue;
         }

+        /* sockdev means the device file is actually
+           a connected socket instead of a disk file.
+           The file name is the socket_spec (host:port)
+           to listen for connections on.
+        */
+        if (strcasecmp( argv[i], "sockdev" ) == 0)
+        {
+            sockdev = true;
+            continue;
+        }
+
         // "%1d:%04X Card: parameter %s in argument %d is invalid"
         WRMSG( HHC01209, "E", LCSS_DEVNUM, argv[i], i+1 );
         return -1;
+
+    } /* end for (i=1; i < argc; i++) Process the driver arguments */
+
+    /* Check for incompatible options... */
+
+    if (sockdev && dev->crlf)
+    {
+        // "%1d:%04X Card: option %s is incompatible"
+        WRMSG( HHC01210, "E", LCSS_DEVNUM,
+            "sockdev/crlf" );
+        return -1;
+    }
+
+    if (sockdev && dev->append)
+    {
+        // "%1d:%04X Card: option %s is incompatible"
+        WRMSG( HHC01210, "E", LCSS_DEVNUM,
+            "sockdev/noclear" );
+        return -1;
+    }
+
+    /* If socket device, create a listening socket
+       to accept connections on.
+    */
+    if (sockdev && !bind_device_ex( dev,
+        dev->filename, onconnect_callback, dev ))
+    {
+        return -1;  // (error msg already issued)
     }

     /* Open the device file right away */
-    if (open_punch( dev ) != 0)
+    if (!sockdev && open_punch( dev ) != 0)
         return -1;  // (error msg already issued)

     return 0;
@@ -180,9 +355,10 @@

     BEGIN_DEVICE_CLASS_QUERY( "PCH", dev, devclass, buflen, buffer );

-    snprintf (buffer, buflen, "%s%s%s%s%s IO[%"PRIu64"]",
+    snprintf (buffer, buflen, "%s%s%s%s%s%s IO[%"PRIu64"]",
                 filename,
                 (dev->ascii                ? " ascii"     : " ebcdic"),
+                (dev->bs                   ? " sockdev"   : ""),
                 ((dev->ascii && dev->crlf) ? " crlf"      : ""),
                 (dev->append               ? " append"    : ""),
                 (dev->stopdev              ? " (stopped)" : ""),
@@ -199,6 +375,10 @@
 int             open_flags;             /* File open flags           */
 off_t           filesize = 0;           /* file size for ftruncate   */

+    /* Socket punch? */
+    if (dev->bs)
+        return (dev->fd < 0 ? -1 : 0);
+
     open_flags = O_WRONLY | O_CREAT /* | O_SYNC */ |  O_BINARY;

     if (!dev->append)
@@ -237,7 +417,18 @@
 {
     /* Close the device file */
     if (dev->fd >= 0)
-        close( dev->fd );
+    {
+        /* Socket punch? */
+        if (dev->bs)
+        {
+            close_socket( dev->fd );
+            // "%1d:%04X Card: client %s, IP %s disconnected from device %s"
+            WRMSG( HHC01211, "I", LCSS_DEVNUM, dev->bs->clientname, dev->bs->clientip, dev->bs->spec );
+        }
+        else
+            close( dev->fd );
+    }
+
     dev->fd = -1;
     dev->stopdev = FALSE;

diff -r -a -x .git -x .vs -x 'msvc.AMD64.*' -x 'msvc.dllmod.*' -x 'msvc.debug.*' -x '*.suo' -x '*.ncb' -x '*.user' -x '*.htm' -x WORK -x DICTS -x FILES -x 'allTests.*' -x '*.rej' -x '*.orig' -x AutoBuildCount.h -x '*.cmp*' -x '*.comp*' -Nu hyperion-git/html/hercconf.html hyperion-1/html/hercconf.html
--- hyperion-git/html/hercconf.html 2023-01-29 08:01:10.687591900 -0800
+++ hyperion-1/html/hercconf.html   2023-04-03 13:21:28.008583100 -0700
@@ -2789,6 +2789,20 @@
         option instead.
         <p>

+    <dt><code>sockdev</code>
+    <dd><p>
+        Indicates the card punch is a socket device wherein the
+        filename is actually a socket specification instead of a
+        device filename.  When used, there must only be one filename
+        specified in the form: <code>port</code> or <code>host:port</code>.
+        The device then accepts
+        remote connections on the given TCP/IP port,
+        and writes data to the socket instead of to a device
+        file. This allows automatic remote spooling of card punch
+        data. The sockdev option is mutually exclusive with the
+        <code>crlf</code> and <code>append</code> options.
+        <p>
+
     </dl>
     <p>

diff -r -a -x .git -x .vs -x 'msvc.AMD64.*' -x 'msvc.dllmod.*' -x 'msvc.debug.*' -x '*.suo' -x '*.ncb' -x '*.user' -x '*.htm' -x WORK -x DICTS -x FILES -x 'allTests.*' -x '*.rej' -x '*.orig' -x AutoBuildCount.h -x '*.cmp*' -x '*.comp*' -Nu hyperion-git/msgenu.h hyperion-1/msgenu.h
--- hyperion-git/msgenu.h   2023-01-29 07:53:19.475214300 -0800
+++ hyperion-1/msgenu.h 2023-04-03 12:55:59.819803700 -0700
@@ -988,7 +988,9 @@
 #define HHC01207 "%1d:%04X Card: file %s: card image exceeds maximum %d bytes"
 #define HHC01208 "%1d:%04X Card: filename is missing"
 #define HHC01209 "%1d:%04X Card: parameter %s in argument %d is invalid"
-//efine HHC01210 - HHC01249 (available)
+#define HHC01210 "%1d:%04X Card: option %s is incompatible"
+#define HHC01211 "%1d:%04X Card: client %s, IP %s disconnected from device %s"
+//efine HHC01212 - HHC01249 (available)

 // reserve 01250 - 01299 for Generic device messages
 #define HHC01250 "%1d:%04X %s: error in function %s: %s"
diff -r -a -x .git -x .vs -x 'msvc.AMD64.*' -x 'msvc.dllmod.*' -x 'msvc.debug.*' -x '*.suo' -x '*.ncb' -x '*.user' -x '*.htm' -x WORK -x DICTS -x FILES -x 'allTests.*' -x '*.rej' -x '*.orig' -x AutoBuildCount.h -x '*.cmp*' -x '*.comp*' -Nu hyperion-git/msvc.makefile.includes/MOD_RULES2.msvc hyperion-1/msvc.makefile.includes/MOD_RULES2.msvc
--- hyperion-git/msvc.makefile.includes/MOD_RULES2.msvc 2023-01-18 13:40:14.242356000 -0800
+++ hyperion-1/msvc.makefile.includes/MOD_RULES2.msvc   2023-04-03 13:16:08.359163300 -0700
@@ -98,7 +98,7 @@
     $(linkdll)
     $(MT_DLL_CMD)

-$(X)hdt3525.dll:  $(O)cardpch.obj \
+$(X)hdt3525.dll:  $(O)cardpch.obj $(O)sockdev.obj \
                   $(O)hengine.lib $(O)hutil.lib $(O)hsys.lib $(O)hercprod.res
     $(linkdll)
     $(MT_DLL_CMD)
diff -r -a -x .git -x .vs -x 'msvc.AMD64.*' -x 'msvc.dllmod.*' -x 'msvc.debug.*' -x '*.suo' -x '*.ncb' -x '*.user' -x '*.htm' -x WORK -x DICTS -x FILES -x 'allTests.*' -x '*.rej' -x '*.orig' -x AutoBuildCount.h -x '*.cmp*' -x '*.comp*' -Nu hyperion-git/printer.c hyperion-1/printer.c
--- hyperion-git/printer.c  2022-11-26 17:01:06.341640000 -0800
+++ hyperion-1/printer.c    2023-04-03 13:03:21.596979600 -0700
@@ -231,8 +231,7 @@
 {
 int rc;

-    /* Write data to the printer file */
-    if (dev->bs)
+    if (dev->bs)    /* socket device? */
     {
         /* Write data to socket, check for error */
         if ((rc = write_socket( dev->fd, buf, len )) < len)
@@ -253,7 +252,7 @@
             return *unitstat = CSW_CE | CSW_DE | CSW_UC;
         }
     }
-    else
+    else /* regular print file */
     {
         /* Write data to the printer file, check for error */
         if ((rc = write( dev->fd, buf, len )) < len)
Fish-Git commented 1 year ago

Never mind. I used netcat to test it with. Works fine.

Closed by commit 133dafcfa090f1eda3f061995f1e4c834a218e79.