AmiTCP net.lib and SocketBaseTagList Tags

bsdsocket.library is a shared library, but much of the Unix programming model (a global errno, select(), perror(), stat(), line-oriented socket reads) lives in a link library. The AmiTCP/IP SDK 4.3 (AmiTCP/IP Group, NSDi, 1994–1996) ships that link library as net.lib and documents it in doc/netlib.doc. This article covers what net.lib provides and how its auto-initialisation works. It then lists every SocketBaseTagList() tag code (the AmiTCP set plus the Roadshow extensions in the NDK), shows how to wire up errno and break signals by hand, and ends with a minimal HTTP client built without any link library, based on the SDK's src/examples/httpget.

The socket calls themselves are covered in Roadshow — bsdsocket TCP/IP Stack.

The SDK's netlib/ drawer holds SAS/C-built libraries. src/netlib/Smakefile shows how the variants are made:

Library Build Use
net.lib Default (near data) Normal programs with SAS/C stdio
netd.lib DEF=USE_DOSIO Programs that use the dos.library stdio macros of <netinclude:stdio.h> instead of C stdio
netnb.lib, netdnb.lib DATA=FAR "No base": for code that cannot use base-relative data
sana2.lib, sana2nb.lib SANA-II error strings (see netinfo.device and sana2.lib)
rpcr.lib, rpcdr.lib Sun RPC client/server library

netd.lib provides _STI_500_dosio_init(), which fills the file table for the USE_DOSIO macros, plus SPrintf(), VSPrintf(), CSPrintf() and VCSPrintf(). Those macros cover stdin, stdout and stderr only. There is no fopen().

Auto-initialisation

net.lib opens libraries with SAS/C 6 auto-initialisation functions, so a program can call socket functions without opening anything itself.

Function Opens Linked in when
_STI_200_openSockets() / _STD_200_closeSockets() bsdsocket.library (minimum version 4) into SocketBase The program references SocketBase
_STI_200_openTimer() / _STD_200_closeTimer() timer.device into TimerBase; sets up timezone/daylight/tzname Needed by gettimeofday() and utime()
_STI_200_openUserGroup() / _STD_200_closeUserGroup() usergroup.library into UserGroupBase Linked with usr.lib

_STI_200_openSockets() checks for Kickstart V37 and opens the library. It then makes one SocketBaseTags() call that links the program's errno and h_errno and passes the program name for syslog():

if (SocketBaseTags(SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(errno))), &errno,
                   SBTM_SETVAL(SBTC_HERRNOLONGPTR), &h_errno,
                   SBTM_SETVAL(SBTC_LOGTAGPTR), _ProgramName,
                   TAG_END))
  exit(30);

If the library is missing, it shows an EasyRequest() ("AmiTCP/IP version 4 or later must be started first.") and exits with 20. With compilers that lack SAS/C auto-init, call the functions yourself:

/* at start of main() */
atexit(_STD_200_closeSockets);
if (_STI_200_openSockets() != 0)
    exit(20);

SAS/C version gotcha: an auto-init function can only abort with exit() under SAS/C 6.3, but under 6.50 and later an exit() from auto-init skips the auto-termination functions. The two compilers therefore need separately compiled net.lib builds. The source handles it with #if __VERSION__ > 6 || __REVISION__ >= 50 → #define exit(x) return(x).

The time zone comes from locale.library when available, otherwise from the TZ variable (zzznnnddd, e.g. hours west of Greenwich), otherwise GMT. gettimeofday() uses the V36 timer.device GetSysTime(), and TimerBase must be open.

net.lib function overview

Socket helpers

Function Purpose
select() Stub: WaitSelect(nfds, r, w, e, timeout, NULL)
inet_ntoa(), inet_makeaddr(), inet_lnaof(), inet_netof() Stubs over Inet_NtoA(), Inet_MakeAddr(), Inet_LnaOf(), Inet_NetOf() that take and return struct in_addr (inlined under SAS/C with _OPTINLINE)
serveraccept(name, &peer) Bind a TCP port by service name or number, listen and accept one connection. Returns the socket or −1. "Meant for daemon developing".
init_inet_daemon() Obtain the socket passed by inetd. Fails with ENXIO if not started by inetd. If ObtainSocket() fails it calls exit() so inetd can clean up.
set_socket_stdio(sock) dup2() the socket onto stdin/stdout/stderr and close the original.
rcmd(), rresvport() BSD remote command (rsh protocol) and privileged-port socket (ports 0–1023, super-user only; EAGAIN means all ports are in use).
herror(banner) Print the resolver error for h_errno, using SBTC_HERRNOSTRPTR.
perror(), strerror() errno texts for <sys/errno.h> codes.
PrintNetFault(code, banner), PrintUserFault(code, banner) Same, but written with dos.library I/O to the error/Output() stream; PrintUserFault is recommended when C stdio is not otherwise used.
openlog(), closelog(), setlogmask() syslog set-up via SBTC_LOGTAGPTR, SBTC_LOGSTAT, SBTC_LOGFACILITY, SBTC_LOGMASK. openlog() stores only the ident pointer.

POSIX compatibility

Function Notes from the autodoc
chmod(), fchmod() Unix mode bits mapped onto Amiga protection. Archive and Script are cleared, and Delete follows Write.
chown() Owner/group change (−1 leaves one unchanged).
stat(), lstat(), fstat() Fill a Unix struct stat that also carries st_dosmode, st_type and st_comment. All three times are the modification time. fstat() on a socket returns a zeroed buffer.
dup(), dup2() The SAS/C UFB implementation can duplicate only sockets.
gettimeofday() struct timeval since 1.1.1970 GMT.
sleep(), usleep() One select() with only a timeout; aborted by the break signals (Ctrl-C by default).
utime() Only modtime is used, since AmigaDOS has one date stamp.
popen(), pclose() Pipe to a command through PIPE:; at most 6 pipes.
getpid(), getopt(), SPrintf() SPrintf() uses RawDoFmt() and assumes output under 1024 characters, with no overflow check.

charRead() and lineRead()

These two readers avoid one recv() per byte or per line.

charRead (<charread.h>, macros only, buffer RC_BUFSIZE = 1024 by default):

struct CharRead rc;
int c;                         /* must be int: compare with negative codes */

initCharRead(&rc, sock);
while ((c = charRead(&rc)) >= 0)
    handle(c);
/* c == RC_DO_SELECT (-3): buffer drained, select() before the next call
   c == RC_EOF       (-2): end of stream
   c == RC_ERROR     (-1): recv() failed, check Errno() */

The macro uses recv(fd, buf, RC_BUFSIZE, 0) when AMIGA is defined and read() otherwise, so the header also works on Unix. Store the result in an int: as the autodoc puts it, "−3 equals 253 if of type unsigned char".

lineRead (<lineread.h>, function in net.lib):

struct LineRead *rl = AllocMem(sizeof(*rl), 0);
initLineRead(rl, sock, RL_LFREQLF, RL_BUFSIZE);

/* after select() says sock is readable */
while ((len = lineRead(rl)) != 0) {        /* 0: incomplete, select() again;
                                              right after select(): EOF */
    if (len > 0)
        process(rl->rl_Line, len);
    else if (rl->rl_Line == NULL)
        break;                             /* -1 and NULL: error */
    else
        overflow(rl->rl_Line);             /* -1: line longer than buffer */
}
lftype Behaviour
RL_LFNOTREQ (0) Return complete lines, or the remaining buffer if it holds no newline.
RL_LFREQLF (1) Lines always end with \n (unless longer than the buffer). Partial data is moved to the start of the buffer.
RL_LFREQNUL (2) As RL_LFREQLF, but the newline is replaced by NUL. The length includes it, so an empty line returns 1.

Gotcha: the lineRead example in netlib.doc passes LF_REQLF, but the enumerators in <lineread.h> are named RL_LFNOTREQ, RL_LFREQLF and RL_LFREQNUL. Always pass RL_BUFSIZE as the buffer size, because struct LineRead embeds a buffer of that size. To use a smaller buffer, #define RL_BUFSIZE before including the header.

SocketBaseTagList()

LONG SocketBaseTagList(struct TagItem *tags);   /* A0, LVO -294 */
LONG SocketBaseTags(Tag first_tag, ...);

This is the per-opener configuration call of bsdsocket.library. The result is 0 on success, or the 1-based index of the first failing tag. It is not a boolean.

Tag encoding

A tag value is not a plain constant. It packs a code with access flags:

Bits Meaning Constant
31 TAG_USER (kept set for tag-list conventions)
15 By reference: ti_Data points to the value SBTF_REF = 0x8000 (SBTF_VAL = 0)
1–14 Tag code (SBTC_*) SBTB_CODE = 1, SBTS_CODE = 0x3FFF
0 Set (1) or get (0) SBTF_SET = 1 (SBTF_GET = 0 in Roadshow)
#define SBTM_GETREF(code) (TAG_USER | SBTF_REF | (((code) & SBTS_CODE) << SBTB_CODE))
#define SBTM_GETVAL(code) (TAG_USER | (((code) & SBTS_CODE) << SBTB_CODE))
#define SBTM_SETREF(code) (TAG_USER | SBTF_REF | (((code) & SBTS_CODE) << SBTB_CODE) | SBTF_SET)
#define SBTM_SETVAL(code) (TAG_USER | (((code) & SBTS_CODE) << SBTB_CODE) | SBTF_SET)

AmiTCP keeps bits 16–30 clear because utility/tagitem.h reserves them. A GETVAL query writes its result back into ti_Data of your tag list, so the list must be writable. The data is always a LONG at an even address. A tag with a PTR suffix takes a pointer as its value: SBTM_SETVAL(SBTC_ERRNOPTR(...)), &errno passes the pointer by value. Passing it by reference would mean a pointer to the pointer.

Headers: AmiTCP SDK 4.3 has the tags in <amitcp/socketbasetags.h>. In the NDK's Roadshow netinclude/ they are in <libraries/bsdsocket.h>, and <bsdsocket/socketbasetags.h> is a "Miami compatibility header" that only includes it.

Tag codes

AmiTCP V4 API (both SDKs):

Code Name Data
1 SBTC_BREAKMASK Exec signal mask that acts as SIGINT. Default SIGBREAKF_CTRL_C.
2 SBTC_SIGIOMASK Signal for asynchronous socket events (deprecated in Roadshow)
3 SBTC_SIGURGMASK Signal for out-of-band data (deprecated in Roadshow)
4 SBTC_SIGEVENTMASK Signal for GetSocketEvents() (V4 addition)
6 SBTC_ERRNO Current errno value
7 SBTC_HERRNO Current h_errno value
8 SBTC_DTABLESIZE Socket descriptor table size (Roadshow default 256)
9 SBTC_FDCALLBACK Link-library fd allocation callback fd = cb(fd, action) (D0/D1, actions FDCB_FREE 0, FDCB_ALLOC 1, FDCB_CHECK 2). Do not use in new code.
10 SBTC_LOGSTAT syslog options
11 SBTC_LOGTAGPTR syslog ident string (default NULL)
12 SBTC_LOGFACILITY syslog facility (default LOG_USER)
13 SBTC_LOGMASK syslog priority mask (default 0xFF)
14 SBTC_ERRNOSTRPTR In: error number; out: string pointer (get only)
15 SBTC_HERRNOSTRPTR Same, for resolver errors
16 SBTC_IOERRNOSTRPTR Same, for Exec I/O errors (pass them negated, i.e. positive). Deprecated in Roadshow.
17 SBTC_S2ERRNOSTRPTR Same, for SANA-II io_Error (deprecated in Roadshow)
18 SBTC_S2WERRNOSTRPTR Same, for SANA-II wire errors (deprecated in Roadshow)
21 SBTC_ERRNOBYTEPTR Link a 1-byte errno (set only)
22 SBTC_ERRNOWORDPTR Link a 2-byte errno
24 SBTC_ERRNOLONGPTR Link a 4-byte errno
25 SBTC_HERRNOLONGPTR Link h_errno (must be a long)
29 SBTC_RELEASESTRPTR Stack name/version string (read only)

SBTC_ERRNOPTR(size) expands to code 21, 22 or 24. An invalid size gives code 0, which makes the call fail.

Roadshow extensions (NDK <libraries/bsdsocket.h>). Older stacks do not process these tags, so check the return value:

Code Name Data
40 SBTC_NUM_PACKET_FILTER_CHANNELS Number of BPF channels (check before bpf_open())
41 SBTC_HAVE_ROUTING_API TRUE if AddRouteTagList() etc. exist
42 SBTC_UDP_CHECKSUM UDP checksums on/off (default on; stack-wide)
43 SBTC_IP_FORWARDING IP forwarding (default off; stack-wide)
44 SBTC_IP_DEFAULT_TTL Default TTL (64; stack-wide)
45 SBTC_ICMP_MASK_REPLY Answer ICMP mask requests (default off)
46 SBTC_ICMP_SEND_REDIRECTS Send redirects when forwarding (default on)
47 SBTC_HAVE_INTERFACE_API Interface API present
48 SBTC_ICMP_PROCESS_ECHO IR_Process 0 / IR_Ignore 1 / IR_Drop 2
49 SBTC_ICMP_PROCESS_TSTAMP Same choices, for time stamp requests
50 SBTC_HAVE_MONITORING_API Monitoring hooks present
51 SBTC_CAN_SHARE_LIBRARY_BASES Allow several tasks to share this base
52 SBTC_LOG_FILE_NAME Log file name (about 510 characters maximum; empty turns logging off)
53 SBTC_HAVE_STATUS_API GetNetworkStatistics() present
54 SBTC_HAVE_DNS_API DNS API present
55 SBTC_LOG_HOOK Hook that replaces the log output
56 SBTC_SYSTEM_STATUS SBSYSSTAT_* flags (see below)
57 SBTC_SIG_ADDRESS_CHANGE_MASK Signal when an interface address changes (default 0)
58 SBTC_IPF_API_VERSION IP filter API version
59 SBTC_HAVE_LOCAL_DATABASE_API setnetent(), getprotoent() … present
60 SBTC_HAVE_ADDRESS_CONVERSION_API inet_aton(), inet_pton(), inet_ntop() present
61 SBTC_HAVE_KERNEL_MEMORY_API mbuf_*() present
62 SBTC_IP_FILTER_HOOK Hook called for every datagram in and out
63 SBTC_HAVE_SERVER_API ProcessIsServer(), ObtainServerSocket() present
64 SBTC_GET_BYTES_RECEIVED Total bytes received, as SBQUAD_T (by reference)
65 SBTC_GET_BYTES_SENT Total bytes sent, as SBQUAD_T (by reference)
66 SBTC_IDN_DEFAULT_CHARACTER_SET IDNCS_ASCII 0 / IDNCS_ISO_8859_LATIN_1 1 (default)
67 SBTC_HAVE_ROADSHOWDATA_API ObtainRoadshowData() present
68 SBTC_ERROR_HOOK Hook called when errno/h_errno change
69 SBTC_HAVE_GETHOSTADDR_R_API gethostbyname_r()/gethostbyaddr_r() present

SBTC_SYSTEM_STATUS flags: SBSYSSTAT_Interfaces (1<<0, interfaces other than loopback are configured and up), SBSYSSTAT_PTP_Interfaces (1<<1), SBSYSSTAT_BCast_Interfaces (1<<2), SBSYSSTAT_Resolver (1<<3), SBSYSSTAT_Routes (1<<4), SBSYSSTAT_DefaultRoute (1<<5). SBQUAD_T is { ULONG sbq_High; ULONG sbq_Low; }.

SBTC_ERROR_HOOK calls LONG hook(struct Hook *, APTR reserved /* NULL */, struct ErrorHookMsg *). The message holds ehm_Size (≥ 12, check it), ehm_Action (EHMA_Set_errno 1 or EHMA_Set_h_errno 2; ignore others) and ehm_Code. Together with SBTC_CAN_SHARE_LIBRARY_BASES it lets several processes sharing one base each get their own error codes. Without the hook, error reporting on a shared base "will become rather difficult", and the SIGIO/SIGURG/SIGEVENT signals reach only the process that set them.

errno and signal setup by hand

Without net.lib, link your variables yourself right after OpenLibrary():

#include <libraries/bsdsocket.h>   /* Roadshow; <amitcp/socketbasetags.h> for AmiTCP */
#include <proto/socket.h>

LONG errno, h_errno;

if (SocketBaseTags(SBTM_SETVAL(SBTC_ERRNOPTR(sizeof(errno))), &errno,
                   SBTM_SETVAL(SBTC_HERRNOLONGPTR),           &h_errno,
                   SBTM_SETVAL(SBTC_LOGTAGPTR),               "myprog",
                   TAG_END) != 0)
{
    /* very old stack: fall back to Errno() after each failure */
}

SetErrnoPtr(&errno, sizeof(errno)) (A0/D0), Errno() and SetSocketSignals(int_mask, io_mask, urgent_mask) (D0/D1/D2) are the older equivalents. Roadshow's autodoc marks all three deprecated in favour of SBTC_ERRNOPTR, SBTC_ERRNO and the SBTC_BREAKMASK/SBTC_SIGIOMASK/SBTC_SIGURGMASK tags.

Break handling. The break mask applies only while a call is actually waiting (for data to arrive or buffer space to free up). A call with no reason to wait ignores it, so test for Ctrl-C in your own loop as well. A break makes WaitSelect() return −1 with EINTR and leaves the signal set. Pass your own signals through WaitSelect()'s last argument (ULONG *, D1): a user signal with no socket ready returns 0. To disable Ctrl-C checking:

SocketBaseTags(SBTM_SETVAL(SBTC_BREAKMASK), 0, TAG_END);

Error strings without net.lib. SBTC_ERRNOSTRPTR is input/output: in goes the number, out comes the string pointer, in the same ti_Data.

struct TagItem tags[2];
tags[0].ti_Tag  = SBTM_GETVAL(SBTC_ERRNOSTRPTR);
tags[0].ti_Data = error;
tags[1].ti_Tag  = TAG_END;
SocketBaseTagList(tags);
text = (STRPTR)tags[0].ti_Data;

src/examples/httpget/httpget.c is the SDK's "no-link-libraries" example. It has its own start-up entry point, opens dos.library V37 and bsdsocket.library V4 itself, parses URL/A with ReadArgs(), and writes its own strlen()-style helpers. It is built with sc ... Link NoStdIO NoStartUp (SAS/C) or gcc -nostdlib (GCC). The version below keeps that structure and trims the URL parsing to host and path. Like the original, it assumes 32-bit int (net.lib refuses to build with short ints).

/* Must be the first function in the file: there is no startup code. */
int start(void);
int begin(void) { return start(); }

#include <exec/types.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/socket.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <amitcp/socketbasetags.h>   /* Roadshow: <libraries/bsdsocket.h> */

struct ExecBase   *SysBase;
struct DosLibrary *DOSBase;
struct Library    *SocketBase;

#ifdef __GNUC__
#define __saveds
#endif

static void netperror(char *banner)
{
    ULONG taglist[5];

    taglist[0] = SBTM_GETVAL(SBTC_ERRNO);       /* writes errno to [1] */
    taglist[2] = SBTM_GETREF(SBTC_ERRNOSTRPTR); /* reads/writes [1]    */
    taglist[3] = (ULONG)&taglist[1];
    taglist[4] = TAG_END;
    SocketBaseTagList((struct TagItem *)taglist);

    Printf("%s: %s\n", banner, (char *)taglist[1]);
}

static int httpget(char *host, char *path)
{
    struct sockaddr_in addr;
    struct hostent *he;
    char buf[1024];
    LONG sd, n, len;

    for (n = 0; n < sizeof(addr); n++)
        ((char *)&addr)[n] = 0;

    if ((he = gethostbyname(host)) != NULL)
        CopyMem(he->h_addr, &addr.sin_addr, sizeof(addr.sin_addr));
    else if ((addr.sin_addr.s_addr = inet_addr(host)) == (ULONG)-1)
    {
        Printf("Cannot resolve host address\n");
        return 20;
    }
    addr.sin_family = AF_INET;
    addr.sin_port   = htons(80);

    if ((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        netperror("socket");
        return 20;
    }
    if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        netperror("connect");
        CloseSocket(sd);
        return 20;
    }

    for (len = 0; path[len] != '\0'; len++)
        ;
    if (send(sd, "GET ", 4, 0) != 4 ||
        send(sd, path, len, 0) != len ||
        send(sd, "\n", 1, 0) != 1)
    {
        netperror("send");
        CloseSocket(sd);
        return 20;
    }

    while ((n = recv(sd, buf, sizeof(buf), 0)) > 0)
        Write(Output(), buf, n);
    if (n != 0)
        netperror("recv");

    CloseSocket(sd);              /* never close(): sockets are not files */
    return n == 0 ? 0 : 20;
}

int __saveds start(void)
{
    int rv = 20;
    SysBase = *(struct ExecBase **)4;

    if ((DOSBase = (struct DosLibrary *)OpenLibrary("dos.library", 37)) != NULL)
    {
        if ((SocketBase = OpenLibrary("bsdsocket.library", 4)) != NULL)
        {
            LONG args[2] = { 0, 0 };
            struct RDArgs *rda = ReadArgs("HOST/A,PATH/A", args, NULL);

            if (rda != NULL)
            {
                rv = httpget((char *)args[0], (char *)args[1]);
                FreeArgs(rda);
            }
            else
                PrintFault(IoErr(), "httpget");

            CloseLibrary(SocketBase);
        }
        else
            Printf("Cannot open bsdsocket.library\n");

        CloseLibrary((struct Library *)DOSBase);
    }
    return rv;
}

Points this example shows:

  • No errno link is needed if you only read errors through SBTC_ERRNO. The netperror() trick chains two tags: the first GETVAL writes errno into slot 1, and the second (by reference to slot 1) turns that number into a string pointer in the same slot. The original comments describe it as a way to "demonstrate the power of SocketBaseTagList()".
  • The original sends "GET " followed by the path and a newline (an HTTP/0.9-style request) and reads until recv() returns 0.
  • It sets SO_KEEPALIVE with setsockopt() before connect(). The code above leaves that out.
  • SocketBase must be opened by each task that calls the library, since every opener gets its own base and error state, unless you use the Roadshow SBTC_CAN_SHARE_LIBRARY_BASES extension.
  • Keep netinclude/ as a separate include path (IDIR=netinclude: in the Smakefile, -Inetinclude: for GCC). See the warning in Roadshow — bsdsocket TCP/IP Stack.

See Also


Sources: AmiTCP SDK 4.3 doc/netlib.doc, netinclude/amitcp/socketbasetags.h, netinclude/charread.h, netinclude/lineread.h, netinclude/clib/netlib_protos.h, netinclude/clib/socket_protos.h, netinclude/fd/socket_lib.fd, src/netlib/autoinit.c, src/netlib/stubs.c, src/netlib/Smakefile, src/examples/httpget/httpget.c, Makefile, Smakefile; NDK 3.2 R4 SANA+RoadshowTCP-IP/netinclude/libraries/bsdsocket.h, netinclude/bsdsocket/socketbasetags.h, doc/bsdsocket.doc, sfd/bsdsocket_lib.sfd Updated: 2026-11-13