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 link libraries in the SDK¶
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 anexit()from auto-init skips the auto-termination functions. The two compilers therefore need separately compilednet.libbuilds. 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
lineReadexample innetlib.docpassesLF_REQLF, but the enumerators in<lineread.h>are namedRL_LFNOTREQ,RL_LFREQLFandRL_LFREQNUL. Always passRL_BUFSIZEas the buffer size, becausestruct LineReadembeds a buffer of that size. To use a smaller buffer,#define RL_BUFSIZEbefore 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;
A minimal client without link libraries¶
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
errnolink is needed if you only read errors throughSBTC_ERRNO. Thenetperror()trick chains two tags: the firstGETVALwriteserrnointo 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 ofSocketBaseTagList()". - The original sends
"GET "followed by the path and a newline (an HTTP/0.9-style request) and reads untilrecv()returns 0. - It sets
SO_KEEPALIVEwithsetsockopt()beforeconnect(). The code above leaves that out. SocketBasemust be opened by each task that calls the library, since every opener gets its own base and error state, unless you use the RoadshowSBTC_CAN_SHARE_LIBRARY_BASESextension.- 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¶
- Roadshow — bsdsocket TCP/IP Stack
- Roadshow Commands and usergroup.library
- netinfo.device and sana2.lib
- SAS/C Startup Modules
- utility.library
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