blob: 8966e71b8e323675d23a323dc280f54681b27adb [file] [log] [blame]
(* ClientSocket.mod provides a client TCP interface for ChanId's.
Copyright (C) 2008-2025 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.
This file is part of GNU Modula-2.
GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Modula-2 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
General Public License for more details.
Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.
You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively. If not, see
<http://www.gnu.org/licenses/>. *)
IMPLEMENTATION MODULE ClientSocket ;
FROM ASCII IMPORT nul, lf, cr ;
FROM ChanConsts IMPORT ChanFlags ;
FROM RTio IMPORT GetDeviceId ;
FROM RTgenif IMPORT GenDevIF, InitGenDevIF ;
FROM RTdata IMPORT ModuleId, MakeModuleId, InitData, GetData, KillData ;
FROM IOChan IMPORT ChanExceptions, InvalidChan, CurrentFlags ;
FROM IOConsts IMPORT ReadResults ;
FROM IOLink IMPORT DeviceId, DeviceTable, DeviceTablePtr, DeviceTablePtrValue, IsDevice,
AllocateDeviceId, RAISEdevException, MakeChan, UnMakeChan ;
FROM Storage IMPORT ALLOCATE, DEALLOCATE ;
FROM Strings IMPORT Append ;
FROM SYSTEM IMPORT ADDRESS, ADR, LOC ;
FROM libc IMPORT read, write, close ;
FROM errno IMPORT geterrno ;
FROM ErrnoCategory IMPORT GetOpenResults ;
FROM WholeStr IMPORT IntToStr ;
FROM RTgen IMPORT ChanDev, DeviceType, InitChanDev,
doLook, doSkip, doSkipLook, doWriteLn,
doReadText, doWriteText, doReadLocs, doWriteLocs,
checkErrno ;
FROM wrapsock IMPORT clientInfo, clientOpen, clientOpenIP, getClientPortNo,
getClientSocketFd, getClientIP, getSizeOfClientInfo,
getPushBackChar, setPushBackChar, getClientHostname ;
TYPE
PtrToLoc = POINTER TO LOC ;
ClientInfo = ADDRESS ;
VAR
mid : ModuleId ;
did : DeviceId ;
dev : ChanDev ;
ClientInfoSize: CARDINAL ;
PROCEDURE look (d: DeviceTablePtr;
VAR ch: CHAR; VAR r: ReadResults) ;
BEGIN
doLook(dev, d, ch, r)
END look ;
PROCEDURE skip (d: DeviceTablePtr) ;
BEGIN
doSkip(dev, d)
END skip ;
PROCEDURE skiplook (d: DeviceTablePtr;
VAR ch: CHAR; VAR r: ReadResults) ;
BEGIN
doSkipLook(dev, d, ch, r)
END skiplook ;
PROCEDURE lnwrite (d: DeviceTablePtr) ;
BEGIN
doWriteLn(dev, d)
END lnwrite ;
PROCEDURE textread (d: DeviceTablePtr;
to: ADDRESS;
maxChars: CARDINAL;
VAR charsRead: CARDINAL) ;
BEGIN
doReadText(dev, d, to, maxChars, charsRead)
END textread ;
PROCEDURE textwrite (d: DeviceTablePtr;
from: ADDRESS;
charsToWrite: CARDINAL);
BEGIN
doWriteText(dev, d, from, charsToWrite)
END textwrite ;
PROCEDURE rawread (d: DeviceTablePtr;
to: ADDRESS;
maxLocs: CARDINAL;
VAR locsRead: CARDINAL) ;
BEGIN
doReadLocs(dev, d, to, maxLocs, locsRead)
END rawread ;
PROCEDURE rawwrite (d: DeviceTablePtr;
from: ADDRESS;
locsToWrite: CARDINAL) ;
BEGIN
doWriteLocs(dev, d, from, locsToWrite)
END rawwrite ;
(*
doreadchar - returns a CHAR from the file associated with, g.
*)
PROCEDURE doreadchar (g: GenDevIF; d: DeviceTablePtr) : CHAR ;
VAR
i : INTEGER ;
fd: INTEGER ;
c : ClientInfo ;
ch: CHAR ;
BEGIN
c := GetData(d, mid) ;
WITH d^ DO
fd := getClientSocketFd(c) ;
IF NOT getPushBackChar(c, ch)
THEN
REPEAT
i := read(fd, ADR(ch), SIZE(ch))
UNTIL i#0 ;
IF i<0
THEN
errNum := geterrno()
END
END ;
RETURN( ch )
END
END doreadchar ;
(*
dounreadchar - pushes a CHAR back onto the file associated with, g.
*)
PROCEDURE dounreadchar (g: GenDevIF; d: DeviceTablePtr; ch: CHAR) : CHAR ;
VAR
fd: INTEGER ;
c : ClientInfo ;
BEGIN
c := GetData(d, mid) ;
WITH d^ DO
fd := getClientSocketFd(c) ;
IF NOT setPushBackChar(c, ch)
THEN
RAISEdevException(cid, did, notAvailable,
'ClientSocket.dounreadchar: number of characters pushed back exceeds buffer')
END ;
RETURN( ch )
END
END dounreadchar ;
(*
dogeterrno - returns the errno relating to the generic device.
*)
PROCEDURE dogeterrno (g: GenDevIF; d: DeviceTablePtr) : INTEGER ;
BEGIN
RETURN geterrno()
END dogeterrno ;
(*
dorbytes - reads upto, max, bytes setting, actual, and
returning FALSE if an error (not due to eof)
occurred.
*)
PROCEDURE dorbytes (g: GenDevIF; d: DeviceTablePtr;
to: ADDRESS;
max: CARDINAL;
VAR actual: CARDINAL) : BOOLEAN ;
VAR
fd: INTEGER ;
c : ClientInfo ;
p : PtrToLoc ;
i : INTEGER ;
BEGIN
c := GetData(d, mid) ;
WITH d^ DO
IF max>0
THEN
p := to ;
IF getPushBackChar(c, p^)
THEN
actual := 1 ;
RETURN( TRUE )
END ;
fd := getClientSocketFd(c) ;
i := read(fd, p, max) ;
IF i>=0
THEN
actual := i ;
RETURN( TRUE )
ELSE
errNum := geterrno() ;
actual := 0 ;
RETURN( FALSE )
END
ELSE
RETURN( FALSE )
END
END
END dorbytes ;
(*
dowbytes - attempts to write out nBytes. The actual
number of bytes written are returned.
If the actual number of bytes written is >= 0 then
the return result will be true. Failure to
write any bytes results in returning FALSE
errno set and the actual will be set to zero.
*)
PROCEDURE dowbytes (g: GenDevIF; d: DeviceTablePtr;
from: ADDRESS;
nBytes: CARDINAL;
VAR actual: CARDINAL) : BOOLEAN ;
VAR
fd: INTEGER ;
c : ClientInfo ;
i : INTEGER ;
BEGIN
c := GetData(d, mid) ;
WITH d^ DO
fd := getClientSocketFd(c) ;
i := write(fd, from, nBytes) ;
IF i>=0
THEN
actual := i ;
RETURN( TRUE )
ELSE
errNum := geterrno() ;
actual := 0 ;
RETURN( FALSE )
END
END
END dowbytes ;
(*
dowriteln - attempt to write an end of line marker to the
file and returns TRUE if successful.
*)
PROCEDURE dowriteln (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ;
VAR
a: ARRAY [0..1] OF CHAR ;
i: CARDINAL ;
BEGIN
a[0] := cr ;
a[1] := lf ;
RETURN( dowbytes(g, d, ADR(a), SIZE(a), i) AND (i=SIZE(a)) )
END dowriteln ;
(*
iseof - returns TRUE if end of file is seen.
*)
PROCEDURE iseof (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ;
VAR
ch: CHAR ;
BEGIN
ch := doreadchar(g, d) ;
WITH d^ DO
IF errNum=0
THEN
ch := dounreadchar(g, d, ch) ;
RETURN( FALSE )
ELSE
RETURN( TRUE )
END
END
END iseof ;
(*
iseoln - returns TRUE if end of line is seen.
*)
PROCEDURE iseoln (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ;
VAR
ch: CHAR ;
BEGIN
ch := doreadchar(g, d) ;
WITH d^ DO
IF errNum=0
THEN
ch := dounreadchar(g, d, ch) ;
RETURN( ch=lf )
ELSE
RETURN( FALSE )
END
END
END iseoln ;
(*
iserror - returns TRUE if an error was seen on the device.
*)
PROCEDURE iserror (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ;
BEGIN
RETURN( d^.errNum#0 )
END iserror ;
PROCEDURE getname (d: DeviceTablePtr;
VAR a: ARRAY OF CHAR) ;
VAR
c: ClientInfo ;
b: ARRAY [0..6] OF CHAR ;
BEGIN
c := GetData(d, mid) ;
getClientHostname(c, ADR(a), HIGH(a)) ;
Append(':', a) ;
IntToStr(getClientPortNo(c) , b) ;
Append(b, a)
END getname ;
(*
freeData - disposes of, c.
*)
PROCEDURE freeData (c: ClientInfo) ;
BEGIN
DEALLOCATE(c, ClientInfoSize) ;
END freeData ;
(*
handlefree -
*)
PROCEDURE handlefree (d: DeviceTablePtr) ;
VAR
c : ClientInfo ;
fd: INTEGER ;
i : INTEGER ;
BEGIN
c := GetData(d, mid) ;
fd := getClientSocketFd(c) ;
i := close(fd) ;
checkErrno(dev, d) ;
KillData(d, mid)
END handlefree ;
(*
OpenSocket - opens a TCP client connection to host:port.
*)
PROCEDURE OpenSocket (VAR cid: ChanId;
host: ARRAY OF CHAR; port: CARDINAL;
f: FlagSet; VAR res: OpenResults) ;
VAR
d: DeviceTablePtr ;
c: ClientInfo ;
e: INTEGER ;
BEGIN
MakeChan(did, cid) ; (* create new channel *)
ALLOCATE(c, ClientInfoSize) ; (* allocate client socket memory *)
d := DeviceTablePtrValue(cid, did) ;
InitData(d, mid, c, freeData) ; (* attach memory to device and module *)
res := clientOpen(c, ADR(host), LENGTH(host), port) ;
IF res=opened
THEN
e := 0
ELSE
e := geterrno()
END ;
WITH d^ DO
flags := f ;
errNum := e ;
doLook := look ;
doSkip := skip ;
doSkipLook := skiplook ;
doLnWrite := lnwrite ;
doTextRead := textread ;
doTextWrite := textwrite ;
doRawRead := rawread ;
doRawWrite := rawwrite ;
doGetName := getname ;
doFree := handlefree
END
END OpenSocket ;
(*
IsSocket - tests if the channel identified by cid is open as
a client socket stream.
*)
PROCEDURE IsSocket (cid: ChanId) : BOOLEAN ;
BEGIN
RETURN( (cid # NIL) AND (InvalidChan() # cid) AND
(IsDevice(cid, did)) AND
((readFlag IN CurrentFlags(cid)) OR
(writeFlag IN CurrentFlags(cid))) )
END IsSocket ;
(*
Close - if the channel identified by cid is not open to a socket
stream, the exception wrongDevice is raised; otherwise
closes the channel, and assigns the value identifying
the invalid channel to cid.
*)
PROCEDURE Close (VAR cid: ChanId) ;
BEGIN
IF IsSocket(cid)
THEN
UnMakeChan(did, cid) ;
cid := InvalidChan()
ELSE
RAISEdevException(cid, did, wrongDevice,
'ClientSocket.' + __FUNCTION__ +
': channel is not a socket stream')
END
END Close ;
(*
Init -
*)
PROCEDURE Init ;
VAR
gen: GenDevIF ;
BEGIN
MakeModuleId(mid) ;
ClientInfoSize := getSizeOfClientInfo() ;
AllocateDeviceId(did) ;
gen := InitGenDevIF(did, doreadchar, dounreadchar,
dogeterrno, dorbytes, dowbytes,
dowriteln,
iseof, iseoln, iserror) ;
dev := InitChanDev(streamfile, did, gen)
END Init ;
BEGIN
Init
END ClientSocket.