netinfo.device and sana2.lib

The AmiTCP/IP SDK 4.3 contains two small but useful pieces that sit beside bsdsocket.library. netinfo.device is an Exec device that reads and writes the user and group databases, and usergroup.library is built on top of it. sana2.lib is a link library that turns SANA-II error codes into text. The SDK's devtools.doc also documents the build tools it ships (cpp, rpcgen, rcsrev). This article covers all three, from doc/netinfo.doc, doc/sana2lib.doc, doc/devtools.doc, the headers in netinclude/ and the sources in src/netlib/.

These are AmiTCP SDK components. See Roadshow Commands and usergroup.library for the usergroup.library API, which the NDK's Roadshow headers also cover.

netinfo.device

The SDK release notes for AmiTCP/IP 4.0 introduce devs/netinfo.device as a "new device, which handles the actual user/group database files". Its autodoc describes the aim as giving a "uniform and well-defined interface" to network databases, with one unit per database, so clients can be moved to other environments. The autodoc says it currently gives access to the local password and group files, and that file notification support is new since the previous release.

Opening

The device lives at AmiTCP:devs/netinfo.device. Use the header's macro:

#include <devices/netinfo.h>

#define NETINFONAME "AmiTCP:devs/netinfo.device"

#define NETINFO_PASSWD_UNIT    0L
#define NETINFO_GROUP_UNIT     1L
#define NETINFO_UNITS          2L

The I/O request is a struct NetInfoReq. Its layout matches an IOStdReq, but the last field is documented as the search criteria and is used that way by NI_MEMBERS:

struct NetInfoReq {
    struct  Message  io_Message;
    struct  Device  *io_Device;
    struct  Unit    *io_Unit;
    UWORD   io_Command;
    UBYTE   io_Flags;
    BYTE    io_Error;
    ULONG   io_Actual;     /* actual number of bytes transferred */
    ULONG   io_Length;     /* requested number bytes transferred */
    APTR    io_Data;       /* points to data area */
    ULONG   io_Offset;     /* search criteria */
};

Commands

Command Value Inputs Results
CMD_RESET 1.3+ 1 — Restart sequential reading.
CMD_READ 1.3+ 2 io_Data = entry buffer, io_Length = its length Next entry; io_Actual = entry length.
CMD_WRITE 1.3+ 3 io_Data = entry structure Adds the entry, or replaces the entry with the same name.
CMD_UPDATE 1.3+ 4 — Writes changes to permanent storage. Until then, CMD_WRITE changes need not be visible to CMD_READ or saved.
NI_GETBYID CMD_NONSTD+0 (9) io_Data = buffer with the ID member set, io_Length Entry with that UID/GID.
NI_GETBYNAME CMD_NONSTD+1 (10) io_Data = buffer whose name member points to a C string, io_Length Entry with that name.
NI_MEMBERS CMD_NONSTD+2 (11) Group unit only. io_Data = LONG array, io_Length = its size in bytes, io_Offset = pointer to a user name The IDs of the groups that list the user as a member; io_Actual = bytes used.

NI_END (CMD_NONSTD+3) marks the end of the range. Every command returns 0 or an error in io_Error.

Naming gotcha: the autodoc overview calls the search commands NI_BYNAME and NI_BYUID, but <devices/netinfo.h> defines them as NI_GETBYNAME and NI_GETBYID. Use the header names.

Errors

The non-standard error codes reuse BSD errno numbers:

Code Value
NIERR_NOTFOUND 2
NIERR_TOOSMALL 7
NIERR_NOMEM 12
NIERR_ACCESS 13
NIERR_NULL_POINTER 14
NIERR_INVAL 22

Entry structures

struct NetInfoPasswd
{
  UBYTE *pw_name;      /* Username */
  UBYTE *pw_passwd;    /* Encrypted password */
  LONG   pw_uid;       /* User ID */
  LONG   pw_gid;       /* Group ID */
  UBYTE *pw_gecos;     /* Real name etc */
  UBYTE *pw_dir;       /* Home directory */
  UBYTE *pw_shell;     /* Shell */
};

struct NetInfoGroup
{
  UBYTE  *gr_name;     /* Group name */
  UBYTE  *gr_passwd;   /* Password */
  LONG    gr_gid;      /* Group ID */
  UBYTE **gr_mem;      /* Member list */
};

These mirror struct passwd and struct group from <pwd.h> and <grp.h>. That is why usergroup.library can map getpwnam(), getgrgid() and the enumerators onto this device. The usergroup autodoc recommends talking to netinfo.device directly when you need re-entrant access, because the library functions return static buffers, and calls it "more convenient" for multitasking applications.

Example: look up a user by name

#include <exec/types.h>
#include <exec/io.h>
#include <devices/netinfo.h>
#include <proto/exec.h>
#include <proto/dos.h>

/* Room for the structure plus the strings the device fills in. */
static ULONG entry_buffer[256];

BOOL lookup_user(STRPTR login)
{
    struct MsgPort *port;
    struct NetInfoReq *nreq;
    struct NetInfoPasswd *pw = (struct NetInfoPasswd *)entry_buffer;
    BOOL found = FALSE;

    if ((port = CreateMsgPort()) != NULL)
    {
        nreq = (struct NetInfoReq *)CreateIORequest(port, sizeof(*nreq));
        if (nreq != NULL)
        {
            if (OpenDevice(NETINFONAME, NETINFO_PASSWD_UNIT,
                           (struct IORequest *)nreq, 0) == 0)
            {
                pw->pw_name       = login;
                nreq->io_Command  = NI_GETBYNAME;
                nreq->io_Data     = entry_buffer;
                nreq->io_Length   = sizeof(entry_buffer);

                if (DoIO((struct IORequest *)nreq) == 0)
                {
                    Printf("%s uid=%ld gid=%ld home=%s\n",
                           pw->pw_name, pw->pw_uid, pw->pw_gid, pw->pw_dir);
                    found = TRUE;
                }
                CloseDevice((struct IORequest *)nreq);
            }
            DeleteIORequest((struct IORequest *)nreq);
        }
        DeleteMsgPort(port);
    }
    return found;
}

To enumerate the database instead, send CMD_RESET once and then CMD_READ until it fails.

Database file formats

The password unit reads AmiTCP:db/passwd: one record per line, seven fields separated by a bar |:

name|password|uid|gid|gecos|home_dir|shell
  • gecos holds comma-separated sub-fields (full name, office, work phone, home phone), used by finger.
  • home_dir is a full DOS path, and an empty shell means the CLI.
  • The device does not restrict the password format, but usergroup.library uses the Version 7 UNIX (POSIX) / 4.4BSD crypt() format. The MultiUser 1.5 / AS225r2 format also works but is "strongly discouraged". An empty password field lets anyone log in, which the autodoc calls "almost invariably a mistake", and the file should not be readable by unprivileged users.
  • Login names must not start with -, and should avoid upper case and dots, which confuse mailers. No field may contain |.

The group unit reads AmiTCP:db/group: four |-separated fields per line:

group|passwd|gid|member,member,...

The password field is rarely used and is normally *. Members are separated by commas without spaces. A user's primary group from db/passwd applies automatically and does not need to be listed here.

Apart from the password field, the formats are those used by MultiUser Library 1.3 and AS225r2.

sana2.lib — SANA-II error messages

sana2.lib (and its far-data variant sana2nb.lib) is built from src/netlib/sana2perror.c, sana2errlist.c and sana2printfault.c. Its declarations are in the AmiTCP SDK's <net/sana2errno.h>.

#include <devices/sana2.h>
#include <net/sana2errno.h>

void sana2perror(const char *banner, struct IOSana2Req *ios2);     /* stderr  */
void Sana2PrintFault(const char *banner, struct IOSana2Req *ios2); /* Output() */

extern const char * const io_errlist[];       extern const short io_nerr;
extern const char * const sana2io_errlist[];  extern const short sana2io_nerr;
extern const char * const sana2wire_errlist[];extern const short sana2wire_nerr;

Both functions print banner: <io error> and add (<wire error>) when ios2_WireError is non-zero and has a table entry. sana2perror() writes to stderr with fprintf(), so it pulls in C stdio. Sana2PrintFault() uses dos.library VPrintf() to Output(), so it suits programs without stdio. The lookup follows the SANA-II sign convention:

WORD  err  = ios2->ios2_Req.io_Error;
ULONG werr = ios2->ios2_WireError;

if (err >= sana2io_nerr || -err > io_nerr)
    text = io_errlist[0];               /* "Unknown error" */
else if (err < 0)
    text = io_errlist[-err];            /* IOERR_OPENFAIL ... IOERR_SELFTEST */
else
    text = sana2io_errlist[err];        /* S2ERR_NO_ERROR ... S2ERR_TX_FAILURE */
Table Entries Covers
io_errlist 8 0 ("Unknown error") and IOERR_OPENFAIL (−1) … IOERR_SELFTEST (−7), indexed by -err
sana2io_errlist 12 S2ERR_NO_ERROR 0 … S2ERR_TX_FAILURE 11, with placeholders for 2 and 7
sana2wire_errlist 19 S2WERR_GENERIC_ERROR 0 … S2WERR_RCVREL_HDW_ERR 18, with a placeholder for 14

Coverage gotcha: the wire table dates from 1994 and ends at 18. The wire codes 19–23 from the SANA-II R4 proposal (S2WERR_UNIT_DISCONNECTED … S2WERR_AUTHENTICATION_FAILED, see SANA-II Device Driver Reference) fall outside sana2wire_nerr, so these functions print the I/O error alone. Add your own strings if your tool can meet them.

Alternatives on a running stack: SocketBaseTagList() with SBTC_S2ERRNOSTRPTR and SBTC_S2WERRNOSTRPTR returns the stack's own strings. Roadshow's autodoc marks both tags deprecated (see AmiTCP net.lib and SocketBaseTagList Tags).

Typical use in a SANA-II tool:

if (DoIO((struct IORequest *)ios2) != 0)
    Sana2PrintFault("S2_CONFIGINTERFACE", ios2);

Development tools (devtools.doc)

The SDK's bin/ drawer holds three tools documented in doc/devtools.doc.

cpp

The Decus C preprocessor by Martin Minow: cpp [-options] [infile [outfile]], reading stdin/stdout if no files are given (- also means stdin/stdout). Options may be given in either case:

Option Effect
-C Keep comments in the output
-Dname[=value] Define (value defaults to 1)
-E Always return success (fatal errors such as a missing include still fail)
-Idirectory Add an include directory (no space after -I)
-N Do not predefine target symbols. -N -N also drops __LINE__, __FILE__, __DATE__.
-P No #line output
-Stext, -S*text Override the sizes of the basic types (and of pointers) for #if sizeof
-Uname Undefine
-Xnumber Debug CPP itself

It implements a 1984 draft of ANSI C (#elif, '\x' escapes, #if defined, #if sizeof(type), token concatenation with #). The #if evaluator uses signed integers only. rpcgen runs this preprocessor on its input.

rpcgen

The Sun RPC protocol compiler. rpcgen proto.x produces proto.h, proto_xdr.c, proto_svc.c and proto_clnt.c (plus proto_tbl.i with -T). Single outputs are selected with -c, -h, -l, -m, -t, -s nettype or -n netid. Other switches: -a (all files including samples), -Sc/-Ss (client/server sample code), -C (ANSI C, the default), -k (K&R), -N (new style with multiple arguments), -I (inetd-startable server), -K secs (server exit delay, default 120), -b/-5 (SunOS 4.1 or SVR4 style; -b is the default). The preprocessor symbols RPC_HDR, RPC_XDR, RPC_SVC, RPC_CLNT and RPC_TBL are defined for the matching output, and lines starting with % are copied through unchanged. The RPC language does not nest structures. The SDK's src/examples/rpc/ (dir, msg, sort) and help/RPC.guide show it in use with rpcr.lib/rpcdr.lib.

rcsrev

RCSREV NAME/A SOURCE/A POSTFIX ASM=ASMINCLUDE/S DATE/K turns a source file's RCS $Id$ string into a BumpRev-compatible NAME_rev.h (and NAME_rev.i with ASMINCLUDE). The autodoc also lists PREFIX/K. DATE selects CURRENT (default), RCS, SASC (__AMIGADATE__) or DICE (__COMMODORE_DATE__). With the SASC or DICE formats the DATE macro is not defined in the output, VSTRING is not defined with SASC, and neither format can be combined with ASMINCLUDE. Only the Id RCS keyword is recognised.

See Also


Sources: AmiTCP SDK 4.3 doc/netinfo.doc, doc/sana2lib.doc, doc/devtools.doc, doc/usergroup.doc, netinclude/devices/netinfo.h, netinclude/net/sana2errno.h, src/netlib/sana2perror.c, src/netlib/sana2errlist.c, src/netlib/sana2printfault.c, src/netlib/Smakefile, "AmiTCP SDK - Read Me"; NDK 3.2 R4 Include_H/exec/io.h Updated: 2026-11-13