File:  [Coherent Logic Development] / freem / src / io_socket.c
Revision 1.7: download - view: text, annotated - select for diffs
Mon Mar 24 02:56:15 2025 UTC (8 days, 21 hours ago) by snw
Branches: MAIN
CVS tags: v0-62-3, HEAD
Socket I/O compat fixes for OS/2

/*
 *   $Id: io_socket.c,v 1.7 2025/03/24 02:56:15 snw Exp $
 *    socket i/o support
 *
 *  
 *   Author: Serena Willis <snw@coherent-logic.com>
 *    Copyright (C) 1998 MUG Deutschland
 *    Copyright (C) 2020, 2025 Coherent Logic Development LLC
 *
 *
 *   This file is part of FreeM.
 *
 *   FreeM is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   FreeM is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Affero Public License for more details.
 *
 *   You should have received a copy of the GNU Affero Public License
 *   along with FreeM.  If not, see <https://www.gnu.org/licenses/>.
 *
 *   $Log: io_socket.c,v $
 *   Revision 1.7  2025/03/24 02:56:15  snw
 *   Socket I/O compat fixes for OS/2
 *
 *   Revision 1.6  2025/03/24 02:55:26  snw
 *   Socket I/O compat fixes for OS/2
 *
 *   Revision 1.5  2025/03/24 02:54:11  snw
 *   Socket I/O compat fixes for OS/2
 *
 *   Revision 1.4  2025/03/22 18:43:54  snw
 *   Make STRLEN 255 chars and add BIGSTR macro for larger buffers
 *
 *   Revision 1.3  2025/03/09 19:14:25  snw
 *   First phase of REUSE compliance and header reformat
 *
 *
 * SPDX-FileCopyrightText:  (C) 2025 Coherent Logic Development LLC
 * SPDX-License-Identifier: AGPL-3.0-or-later
 **/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <setjmp.h>

#if !defined(MSDOS)
# include <sys/socket.h>
# include <arpa/inet.h>
# include <netdb.h>
#endif

#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(_SCO_DS) && !defined(MSDOS)
# include <netinet/in.h>
#endif

#if !defined(__OpenBSD__) && !defined(__FreeBSD__)
# include <sys/timeb.h>
#endif

#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(_SCO_DS)
# define USE_SYS_TIME_H
#endif

#if defined(_SCO_DS)
# define SHUT_RDWR 2
#endif

#ifdef USE_SYS_TIME_H
#include <sys/time.h>
#endif

#if defined(__APPLE__)
# include <sys/select.h>
#endif

#include "mpsdef.h"
#include "mref.h"
#include "io_socket.h"

io_socket *io_sockets[MAXSCK];

/* channel:     channel number
   addr_string: server:port[:family:[udp|tcp]]*/
short msck_open (int channel, char *addr_string)
{
#if !defined(MSDOS) && !defined(__OS2__)
    char *addr = "";
    char *port = "";
    char *family = "";
    char *typ = "";

    char *finaddr;
    
    struct hostent *he;
    struct in_addr **addr_list;

    int pt = 0;
    int fm = 0;
    int tp = 0;

    short ct = 0;
    register int j = 0;

    short i = channel + FIRSTSCK;   /* get index into io_sockets[] array */

    finaddr = (char *) malloc (256 * sizeof (char));
    NULLPTRCHK(finaddr,"msck_open");

    
    merr_clear_iochan_err (channel);
    
    for (j = 0; j < strlen (addr_string); j++) {
        if (addr_string[j] == ':') ct++;
    }

    if (ct < 1 || ct > 3) {
        merr_raise (ARGLIST);
        merr_set_iochan_err (channel, ARGLIST, "invalid OPEN parameters");
        return -1;
    }

    addr = strtok (addr_string, ":");
    port = strtok (NULL, ":");
    
    if (ct == 1) {

	family = (char *) malloc (256 * sizeof (char));
	NULLPTRCHK(family,"msck_open");
	
        typ = (char *) malloc (256 * sizeof (char));
	NULLPTRCHK(typ,"msck_open");
	
        strcpy (family, "IPV4");
        strcpy (typ, "TCP");
    }

    if (ct == 2) {
        family = strtok (NULL, ":");

	typ = (char *) malloc (256 * sizeof (char));
	NULLPTRCHK(typ,"msck_open");
	
        strcpy (typ, "TCP");
    }

    if (ct == 3) {
        family = strtok (NULL, ":");
        typ = strtok (NULL, ":");
    }

    if (strcmp (family, "IPV4") == 0) {
        fm = AF_INET;
    }
#if !defined(_SCO_DS) && defined(AF_INET6)
    else if (strcmp (family, "IPV6") == 0) {
        fm = AF_INET6;
    }
#endif
    else {
        merr_raise (SCKIFAM);
        merr_set_iochan_err (channel, SCKIFAM, "invalid address family");
        return 0;
    }

    if (strcmp (typ, "TCP") == 0) {
        tp = SOCK_STREAM;
    }
    else if (strcmp (typ, "UDP") == 0) {
        tp = SOCK_DGRAM;
    }
    else {
        merr_raise (SCKITYP);
        return 0;
    }    

    io_sockets[i] = (io_socket *) malloc (sizeof (io_socket));
    NULLPTRCHK(io_sockets[i],"msck_open");
    
    io_sockets[i]->io_channel = channel;
    io_sockets[i]->typ = tp;
    io_sockets[i]->sck = socket (fm, tp, 0);
    io_sockets[i]->srv.sin_family = fm;
    
    pt = atoi (port);

    if (pt < 0 || pt > 65535) {
        merr_raise (SCKIPRT);
        merr_set_iochan_err (channel, SCKIPRT, "invalid port number");
        return 0;
    }

    io_sockets[i]->srv.sin_port = htons (pt);

#if !defined(INADDR_NONE)
# define INADDR_NONE -1
#endif
    
    if (inet_addr (addr) == INADDR_NONE) {
        
        /* addr is not a valid IP. we need to do a nameserver lookup. */
        
        if ((he = gethostbyname (addr)) == NULL) {
            merr_raise (NAMERES);
            merr_set_iochan_err (channel, NAMERES, "name resolution failure");
            return 0;
        }

        addr_list = (struct in_addr **) he->h_addr_list;

        strcpy (finaddr, inet_ntoa (*addr_list[0]));

    }
    else {

        /* this was already a valid IP, so we can just use it as-is. */

        strcpy (finaddr, addr);

    }
    
    io_sockets[i]->srv.sin_addr.s_addr = inet_addr (finaddr);
    io_sockets[i]->connected = FALSE;

    if (io_sockets[i]->sck == -1) {
        merr_raise (SCKCREAT);
        merr_set_iochan_err (channel, SCKCREAT, "error creating socket");
        return 0;
    }

    return i;

#else

    merr_raise (SCKCREAT);
    merr_set_iochan_err (channel, SCKCREAT, "error creating socket");
    return 0;

#endif    

}

short msck_connect (int channel)
{

#if !defined(MSDOS) && !defined(__OS2__)
    
    short i = channel + FIRSTSCK;

    if (io_sockets[i]->typ != SOCK_STREAM) {
        merr_raise (SCKAERR);
        merr_set_iochan_err (channel, SCKAERR, "cannot CONNECT a UDP socket");
        return 0;
    }

    if (io_sockets[i]->connected == TRUE) {
        merr_raise (SCKACON);
        merr_set_iochan_err (channel, SCKACON, "cannot CONNECT previously-connected socket");
        return 0;
    }

    if (connect (io_sockets[i]->sck, (struct sockaddr *) &(io_sockets[i]->srv), sizeof (io_sockets[i]->srv)) < 0) {
        merr_raise (SCKCERR);
        merr_set_iochan_err (channel, SCKCERR, "error in CONNECT");
        return 0;
    }
    else {
        io_sockets[i]->connected = TRUE;
    }

    return i;

#else

    return 0;

#endif
    
}

short msck_write (int channel, char *buf, short length)
{

#if !defined(MSDOS) && !defined(__OS2__)    
    
    ssize_t ct;
    short i = channel + FIRSTSCK;

    if (io_sockets[i]->connected == FALSE && io_sockets[i]->typ != SOCK_DGRAM) {

        /* throw socket not connected error if not doing UDP */
        merr_raise (SCKNCON);
        merr_set_iochan_err (channel, SCKNCON, "TCP socket not connected");
        return 0;

    }

    if ((ct = send (io_sockets[i]->sck, buf, length, 0)) < 0) {
        merr_raise (SCKESND);
        merr_set_iochan_err (channel, SCKESND, "error in WRITE to socket");
        return 0;
    }

    return ct;

#else

    merr_raise (SCKNCON);
    merr_set_iochan_err (channel, SCKNCON, "TCP socket not connected");
    return 0;

#endif    
    
}

short msck_read (int channel, char *buf, long sck_timeout, short sck_timeoutms, short length)
{

#if !defined(MSDOS) && !defined(__OS2__)    
    fd_set fds;
    short i;
    struct timeval t;
    char *terminator;
    char *rdbuf;
    char ch;
    short in_term = 0;
    short termlen = 0;
    ssize_t rcvct = 0;
    ssize_t ct = 0;

    i = channel + FIRSTSCK;
    terminator = (char *) malloc (255 * sizeof (char));
    NULLPTRCHK(terminator,"msck_read");
    
    rdbuf = (char *) malloc (length * sizeof (char));
    NULLPTRCHK(rdbuf,"msck_read");
    
    if (io_sockets[i]->connected == FALSE && io_sockets[i]->typ != SOCK_DGRAM) {
        merr_raise (SCKNCON);
        merr_set_iochan_err (channel, SCKNCON, "TCP socket not connected");
        return 0;
    }

    buf[0] = '\0';

    termlen = msck_get_terminator (channel, terminator);
    
    for (;;) {

        FD_ZERO (&fds);
        FD_SET ((unsigned int) io_sockets[i]->sck, &fds);

        if (sck_timeout == -1) {
            /* wait forever */
            select (io_sockets[i]->sck + 1, &fds, NULL, NULL, NULL);    
        }
        else {
            
            /* set the socket timeout */
            t.tv_sec = sck_timeout;
            t.tv_usec = sck_timeoutms * 1000; 
            
            select (io_sockets[i]->sck + 1, &fds, NULL, NULL, &t);
        
        }

        if (ct >= length) goto read_done;

        if ((rcvct = recv (io_sockets[i]->sck, rdbuf, 1, 0)) < 1) {
            merr_raise (SCKERCV);
            merr_set_iochan_err (channel, SCKERCV, "error in READ from socket");
            return 0;
        }
        else {

            ct++;
        
            ch = rdbuf[0];
            rdbuf[1] = '\0';
        
        }

        strcat (buf, rdbuf);

        if (ch == terminator[0] && termlen == 1) {
            /* 1-char terminator reached. populate $KEY. */
            sprintf (zb, "%c\201", terminator[0]);
            goto read_done;
        }
        else if (ch == terminator[0] && termlen == 2 && !in_term) {
            /* first char of 2-char terminator found. we're now in a terminator. */
            in_term = 1;
        }
        else if (ch == terminator[0] && termlen == 2 && in_term) {
            /* we're in a 2-char terminator, but the second char doesn't match. */
            in_term = 0;
        }
        else if (ch == terminator[1] && termlen == 2 && in_term) {
            /* 2-char terminator reached. populate $KEY */
            sprintf (zb, "%s\201", terminator);
            goto read_done;
        }

    }

read_done:

    stcnv_c2m (buf);

    return ct;

#else

    return 0;

#endif
    
}

short msck_close (int channel)
{

#if !defined(MSDOS) && !defined(__OS2__)    
    short i = channel + FIRSTSCK;


    if (io_sockets[i] != NULL) {

        shutdown (io_sockets[i]->sck, SHUT_RDWR);

        free (io_sockets[i]);

        return 1;
    }
#endif    

    return 0;
    
}

short msck_get_terminator (int channel, char *buf)
{
    char wr_io[9];   
    char *wr_key;// = (char *) malloc (STRLEN * sizeof (char));
    //NULLPTRCHK(wr_key,"msck_get_terminator");
    
    freem_ref_t *wrr = (freem_ref_t *) malloc (sizeof (freem_ref_t));
    NULLPTRCHK(wrr,"msck_get_terminator");
    
    snprintf (wr_io, 8, "%d", channel);

    /* get ^$DEVICE($IO,"TERMINATOR") */

    mref_init (wrr, MREF_RT_SSV, "^$DEVICE");
    mref_set_subscript (wrr, 0, wr_io);
    mref_set_subscript (wrr, 1, "TERMINATOR");

    wr_key = mref_to_internal (wrr);

    ssvn (get_sym, wr_key, buf);

    if (merr () > OK) {

        /* SSV node was undefined. Default to CRLF terminator. */

        sprintf (buf, "\r\n\201");
        merr_raise (OK);

    }

    stcnv_m2c (buf);

    free (wr_key);
    free (wrr);

    return strlen (buf);
}

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>