From fa080de7afc95aa1c19a6e6fc0e0708ced2eadc4 Mon Sep 17 00:00:00 2001 From: Joseph Hunkeler Date: Wed, 8 Jul 2015 20:46:52 -0400 Subject: Initial commit --- unix/os/README | 7 + unix/os/alloc.c | 273 +++ unix/os/dio.c | 9 + unix/os/doc/Mach.notes | 32 + unix/os/doc/os.hd | 71 + unix/os/doc/os.ms | 4249 +++++++++++++++++++++++++++++++++++++++++++++++ unix/os/doc/ostoc.ms | 130 ++ unix/os/doc/zalocd.hlp | 53 + unix/os/doc/zardbf.hlp | 56 + unix/os/doc/zawrbf.hlp | 56 + unix/os/doc/zawset.hlp | 42 + unix/os/doc/zawtbf.hlp | 34 + unix/os/doc/zcall.hlp | 39 + unix/os/doc/zclcpr.hlp | 33 + unix/os/doc/zcldir.hlp | 28 + unix/os/doc/zcldpr.hlp | 38 + unix/os/doc/zclsbf.hlp | 32 + unix/os/doc/zclstx.hlp | 35 + unix/os/doc/zfacss.hlp | 37 + unix/os/doc/zfaloc.hlp | 34 + unix/os/doc/zfchdr.hlp | 29 + unix/os/doc/zfdele.hlp | 29 + unix/os/doc/zfgcwd.hlp | 26 + unix/os/doc/zfinfo.hlp | 66 + unix/os/doc/zfiobf.hlp | 53 + unix/os/doc/zfiolp.hlp | 54 + unix/os/doc/zfiomt.hlp | 65 + unix/os/doc/zfiopr.hlp | 58 + unix/os/doc/zfiosf.hlp | 51 + unix/os/doc/zfiotx.hlp | 44 + unix/os/doc/zfioty.hlp | 75 + unix/os/doc/zflstx.hlp | 33 + unix/os/doc/zfmkcp.hlp | 40 + unix/os/doc/zfpath.hlp | 32 + unix/os/doc/zfprot.hlp | 47 + unix/os/doc/zfrnam.hlp | 40 + unix/os/doc/zfsubd.hlp | 76 + unix/os/doc/zfxdir.hlp | 31 + unix/os/doc/zgettx.hlp | 57 + unix/os/doc/zgfdir.hlp | 37 + unix/os/doc/zgtime.hlp | 28 + unix/os/doc/zgtpid.hlp | 25 + unix/os/doc/zintpr.hlp | 34 + unix/os/doc/zlocpr.hlp | 35 + unix/os/doc/zlocva.hlp | 47 + unix/os/doc/zmain.hlp | 62 + unix/os/doc/zmaloc.hlp | 71 + unix/os/doc/zmfree.hlp | 36 + unix/os/doc/znottx.hlp | 45 + unix/os/doc/zopcpr.hlp | 33 + unix/os/doc/zopdir.hlp | 34 + unix/os/doc/zopdpr.hlp | 37 + unix/os/doc/zopnbf.hlp | 53 + unix/os/doc/zopntx.hlp | 55 + unix/os/doc/zoscmd.hlp | 36 + unix/os/doc/zpanic.hlp | 32 + unix/os/doc/zputtx.hlp | 59 + unix/os/doc/zraloc.hlp | 45 + unix/os/doc/zsektx.hlp | 43 + unix/os/doc/zsttbf.hlp | 53 + unix/os/doc/zstttx.hlp | 50 + unix/os/doc/zsvjmp.hlp | 65 + unix/os/doc/ztslee.hlp | 31 + unix/os/doc/zxgmes.hlp | 35 + unix/os/doc/zxwhen.hlp | 70 + unix/os/doc/zzclmt.hlp | 47 + unix/os/doc/zzopmt.hlp | 62 + unix/os/doc/zzrdmt.hlp | 37 + unix/os/doc/zzrwmt.hlp | 31 + unix/os/doc/zzwrmt.hlp | 36 + unix/os/doc/zzwtmt.hlp | 41 + unix/os/getproc.c | 134 ++ unix/os/gmttolst.c | 73 + unix/os/irafpath.c | 165 ++ unix/os/mkpkg | 98 ++ unix/os/mkpkg.sh | 42 + unix/os/mkproto | 5 + unix/os/net/README | 90 + unix/os/net/accept.c | 26 + unix/os/net/connect.c | 27 + unix/os/net/ctype.h | 4 + unix/os/net/eprintf.c | 15 + unix/os/net/ghostbynm.c | 37 + unix/os/net/ghostent.c | 137 ++ unix/os/net/gsocknm.c | 23 + unix/os/net/hostdb.c | 39 + unix/os/net/htonl.c | 22 + unix/os/net/htons.c | 16 + unix/os/net/in.h | 134 ++ unix/os/net/inetaddr.c | 92 + unix/os/net/kutil.c | 342 ++++ unix/os/net/listen.c | 22 + unix/os/net/mkpkg | 25 + unix/os/net/netdb.h | 44 + unix/os/net/ntohl.c | 22 + unix/os/net/ntohs.c | 16 + unix/os/net/rexec.c | 160 ++ unix/os/net/socket.c | 25 + unix/os/net/socket.h | 109 ++ unix/os/net/tcpclose.c | 16 + unix/os/net/tcpread.c | 26 + unix/os/net/tcpwrite.c | 23 + unix/os/net/types.h | 39 + unix/os/net/zfioks.c | 441 +++++ unix/os/net/zzdebug.x | 92 + unix/os/prwait.c | 175 ++ unix/os/tape.c | 508 ++++++ unix/os/zalloc.c | 206 +++ unix/os/zawset.c | 154 ++ unix/os/zcall.c | 91 + unix/os/zdojmp.c | 38 + unix/os/zfacss.c | 124 ++ unix/os/zfaloc.c | 104 ++ unix/os/zfchdr.c | 57 + unix/os/zfdele.c | 27 + unix/os/zfgcwd.c | 65 + unix/os/zfinfo.c | 99 ++ unix/os/zfiobf.c | 888 ++++++++++ unix/os/zfioks.c | 2101 +++++++++++++++++++++++ unix/os/zfiolp.c | 239 +++ unix/os/zfiomt.c | 1911 +++++++++++++++++++++ unix/os/zfiond.c | 918 ++++++++++ unix/os/zfiopl.c | 279 ++++ unix/os/zfiopr.c | 499 ++++++ unix/os/zfiosf.c | 126 ++ unix/os/zfiotx.c | 991 +++++++++++ unix/os/zfioty.c | 127 ++ unix/os/zflink.c | 45 + unix/os/zfmkcp.c | 71 + unix/os/zfmkdr.c | 44 + unix/os/zfnbrk.c | 63 + unix/os/zfpath.c | 50 + unix/os/zfpoll.c | 129 ++ unix/os/zfprot.c | 103 ++ unix/os/zfrmdr.c | 39 + unix/os/zfrnam.c | 50 + unix/os/zfsubd.c | 104 ++ unix/os/zfunc.c | 80 + unix/os/zfutim.c | 68 + unix/os/zfxdir.c | 51 + unix/os/zgcmdl.c | 91 + unix/os/zghost.c | 25 + unix/os/zglobl.c | 19 + unix/os/zgmtco.c | 49 + unix/os/zgtenv.c | 245 +++ unix/os/zgtime.c | 65 + unix/os/zgtpid.c | 18 + unix/os/zintpr.c | 29 + unix/os/zlocpr.c | 61 + unix/os/zlocva.c | 24 + unix/os/zmain.c | 204 +++ unix/os/zmaloc.c | 39 + unix/os/zmfree.c | 35 + unix/os/zopdir.c | 468 ++++++ unix/os/zopdpr.c | 201 +++ unix/os/zoscmd.c | 219 +++ unix/os/zpanic.c | 103 ++ unix/os/zraloc.c | 37 + unix/os/zshlib.c | 18 + unix/os/zwmsec.c | 109 ++ unix/os/zxwhen.c | 499 ++++++ unix/os/zzdbg.c | 158 ++ unix/os/zzepro.c | 84 + unix/os/zzexit.c | 17 + unix/os/zzpstr.c | 176 ++ unix/os/zzsetk.c | 38 + unix/os/zzstrt.c | 628 +++++++ 167 files changed, 24463 insertions(+) create mode 100644 unix/os/README create mode 100644 unix/os/alloc.c create mode 100644 unix/os/dio.c create mode 100644 unix/os/doc/Mach.notes create mode 100644 unix/os/doc/os.hd create mode 100644 unix/os/doc/os.ms create mode 100644 unix/os/doc/ostoc.ms create mode 100644 unix/os/doc/zalocd.hlp create mode 100644 unix/os/doc/zardbf.hlp create mode 100644 unix/os/doc/zawrbf.hlp create mode 100644 unix/os/doc/zawset.hlp create mode 100644 unix/os/doc/zawtbf.hlp create mode 100644 unix/os/doc/zcall.hlp create mode 100644 unix/os/doc/zclcpr.hlp create mode 100644 unix/os/doc/zcldir.hlp create mode 100644 unix/os/doc/zcldpr.hlp create mode 100644 unix/os/doc/zclsbf.hlp create mode 100644 unix/os/doc/zclstx.hlp create mode 100644 unix/os/doc/zfacss.hlp create mode 100644 unix/os/doc/zfaloc.hlp create mode 100644 unix/os/doc/zfchdr.hlp create mode 100644 unix/os/doc/zfdele.hlp create mode 100644 unix/os/doc/zfgcwd.hlp create mode 100644 unix/os/doc/zfinfo.hlp create mode 100644 unix/os/doc/zfiobf.hlp create mode 100644 unix/os/doc/zfiolp.hlp create mode 100644 unix/os/doc/zfiomt.hlp create mode 100644 unix/os/doc/zfiopr.hlp create mode 100644 unix/os/doc/zfiosf.hlp create mode 100644 unix/os/doc/zfiotx.hlp create mode 100644 unix/os/doc/zfioty.hlp create mode 100644 unix/os/doc/zflstx.hlp create mode 100644 unix/os/doc/zfmkcp.hlp create mode 100644 unix/os/doc/zfpath.hlp create mode 100644 unix/os/doc/zfprot.hlp create mode 100644 unix/os/doc/zfrnam.hlp create mode 100644 unix/os/doc/zfsubd.hlp create mode 100644 unix/os/doc/zfxdir.hlp create mode 100644 unix/os/doc/zgettx.hlp create mode 100644 unix/os/doc/zgfdir.hlp create mode 100644 unix/os/doc/zgtime.hlp create mode 100644 unix/os/doc/zgtpid.hlp create mode 100644 unix/os/doc/zintpr.hlp create mode 100644 unix/os/doc/zlocpr.hlp create mode 100644 unix/os/doc/zlocva.hlp create mode 100644 unix/os/doc/zmain.hlp create mode 100644 unix/os/doc/zmaloc.hlp create mode 100644 unix/os/doc/zmfree.hlp create mode 100644 unix/os/doc/znottx.hlp create mode 100644 unix/os/doc/zopcpr.hlp create mode 100644 unix/os/doc/zopdir.hlp create mode 100644 unix/os/doc/zopdpr.hlp create mode 100644 unix/os/doc/zopnbf.hlp create mode 100644 unix/os/doc/zopntx.hlp create mode 100644 unix/os/doc/zoscmd.hlp create mode 100644 unix/os/doc/zpanic.hlp create mode 100644 unix/os/doc/zputtx.hlp create mode 100644 unix/os/doc/zraloc.hlp create mode 100644 unix/os/doc/zsektx.hlp create mode 100644 unix/os/doc/zsttbf.hlp create mode 100644 unix/os/doc/zstttx.hlp create mode 100644 unix/os/doc/zsvjmp.hlp create mode 100644 unix/os/doc/ztslee.hlp create mode 100644 unix/os/doc/zxgmes.hlp create mode 100644 unix/os/doc/zxwhen.hlp create mode 100644 unix/os/doc/zzclmt.hlp create mode 100644 unix/os/doc/zzopmt.hlp create mode 100644 unix/os/doc/zzrdmt.hlp create mode 100644 unix/os/doc/zzrwmt.hlp create mode 100644 unix/os/doc/zzwrmt.hlp create mode 100644 unix/os/doc/zzwtmt.hlp create mode 100644 unix/os/getproc.c create mode 100644 unix/os/gmttolst.c create mode 100644 unix/os/irafpath.c create mode 100644 unix/os/mkpkg create mode 100644 unix/os/mkpkg.sh create mode 100755 unix/os/mkproto create mode 100644 unix/os/net/README create mode 100644 unix/os/net/accept.c create mode 100644 unix/os/net/connect.c create mode 100644 unix/os/net/ctype.h create mode 100644 unix/os/net/eprintf.c create mode 100644 unix/os/net/ghostbynm.c create mode 100644 unix/os/net/ghostent.c create mode 100644 unix/os/net/gsocknm.c create mode 100644 unix/os/net/hostdb.c create mode 100644 unix/os/net/htonl.c create mode 100644 unix/os/net/htons.c create mode 100644 unix/os/net/in.h create mode 100644 unix/os/net/inetaddr.c create mode 100644 unix/os/net/kutil.c create mode 100644 unix/os/net/listen.c create mode 100644 unix/os/net/mkpkg create mode 100644 unix/os/net/netdb.h create mode 100644 unix/os/net/ntohl.c create mode 100644 unix/os/net/ntohs.c create mode 100644 unix/os/net/rexec.c create mode 100644 unix/os/net/socket.c create mode 100644 unix/os/net/socket.h create mode 100644 unix/os/net/tcpclose.c create mode 100644 unix/os/net/tcpread.c create mode 100644 unix/os/net/tcpwrite.c create mode 100644 unix/os/net/types.h create mode 100644 unix/os/net/zfioks.c create mode 100644 unix/os/net/zzdebug.x create mode 100644 unix/os/prwait.c create mode 100644 unix/os/tape.c create mode 100644 unix/os/zalloc.c create mode 100644 unix/os/zawset.c create mode 100644 unix/os/zcall.c create mode 100644 unix/os/zdojmp.c create mode 100644 unix/os/zfacss.c create mode 100644 unix/os/zfaloc.c create mode 100644 unix/os/zfchdr.c create mode 100644 unix/os/zfdele.c create mode 100644 unix/os/zfgcwd.c create mode 100644 unix/os/zfinfo.c create mode 100644 unix/os/zfiobf.c create mode 100644 unix/os/zfioks.c create mode 100644 unix/os/zfiolp.c create mode 100644 unix/os/zfiomt.c create mode 100644 unix/os/zfiond.c create mode 100644 unix/os/zfiopl.c create mode 100644 unix/os/zfiopr.c create mode 100644 unix/os/zfiosf.c create mode 100644 unix/os/zfiotx.c create mode 100644 unix/os/zfioty.c create mode 100644 unix/os/zflink.c create mode 100644 unix/os/zfmkcp.c create mode 100644 unix/os/zfmkdr.c create mode 100644 unix/os/zfnbrk.c create mode 100644 unix/os/zfpath.c create mode 100644 unix/os/zfpoll.c create mode 100644 unix/os/zfprot.c create mode 100644 unix/os/zfrmdr.c create mode 100644 unix/os/zfrnam.c create mode 100644 unix/os/zfsubd.c create mode 100644 unix/os/zfunc.c create mode 100644 unix/os/zfutim.c create mode 100644 unix/os/zfxdir.c create mode 100644 unix/os/zgcmdl.c create mode 100644 unix/os/zghost.c create mode 100644 unix/os/zglobl.c create mode 100644 unix/os/zgmtco.c create mode 100644 unix/os/zgtenv.c create mode 100644 unix/os/zgtime.c create mode 100644 unix/os/zgtpid.c create mode 100644 unix/os/zintpr.c create mode 100644 unix/os/zlocpr.c create mode 100644 unix/os/zlocva.c create mode 100644 unix/os/zmain.c create mode 100644 unix/os/zmaloc.c create mode 100644 unix/os/zmfree.c create mode 100644 unix/os/zopdir.c create mode 100644 unix/os/zopdpr.c create mode 100644 unix/os/zoscmd.c create mode 100644 unix/os/zpanic.c create mode 100644 unix/os/zraloc.c create mode 100644 unix/os/zshlib.c create mode 100644 unix/os/zwmsec.c create mode 100644 unix/os/zxwhen.c create mode 100644 unix/os/zzdbg.c create mode 100644 unix/os/zzepro.c create mode 100644 unix/os/zzexit.c create mode 100644 unix/os/zzpstr.c create mode 100644 unix/os/zzsetk.c create mode 100644 unix/os/zzstrt.c (limited to 'unix/os') diff --git a/unix/os/README b/unix/os/README new file mode 100644 index 00000000..b097ed92 --- /dev/null +++ b/unix/os/README @@ -0,0 +1,7 @@ +4.2BSD UNIX IRAF Kernel. + + This directory contains the machine dependent routines comprising the IRAF +system interface to UNIX 4.2BSD. Some additional potentially machine dependent +routines (the bit and byte primitives) are in sys$osb. Documentation is in +the System Interface Reference manual doc$Os.ms and the manual pages for the +individual routines are in the subdirectory ./doc. diff --git a/unix/os/alloc.c b/unix/os/alloc.c new file mode 100644 index 00000000..d73c3e9c --- /dev/null +++ b/unix/os/alloc.c @@ -0,0 +1,273 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define import_spp +#define import_alloc +#define import_knames +#include + +/* + * ALLOC -- Unix task to allocate and deallocate devices given their generic + * name. These names are associated with special files in the ALLOCFILE file. + * Allocation is accomplished by setting the device file owner and permissions + * for the /dev entries associated with a particular logical device. + * Although we are called by the IRAF kernel, we are implemented as a task + * rather than as a function since super user permission is required to modify + * directory entries in /dev. + * + * usage: + * $hbin/alloc.e -a aliases allocate device + * $hbin/alloc.e -d aliases deallocate device + * $hbin/alloc.e -s aliases get allocation status + * + * Here, "aliases" are the names of all entries in /dev for the physical device, + * e.g., "mt8", "rmt8", "nrmt8", etc. For security reasons, only device + * files in /dev can be allocated, and the user must already have RW perm + * on the device file. Allocating the file simply changes the file + * ownership to the uid of the caller, and removes access permissions for + * group and world. + * + * NOTE: THIS TASK MUST BE INSTALLED IN HLIB WITH OWNER=ROOT AND + * "set uid on execution" PERMISSION (see mkpkg). + */ + +#define NSFILES 30 /* max number spec files assoc w/dev */ +#define RWOWN 0600 /* -rw------ */ +#define RWALL 0666 /* -rw-rw-rw */ + +struct file { /* special files for "device" */ + char f_name[SZ_FNAME]; /* file name */ + struct stat f_sbuf; /* stat buffer */ +} sfiles[NSFILES]; + +int debug=0; +int nsfiles; /* number of special files */ +int mode; /* 07 mode, ie, 04, 02, or 06 */ + + +/* System prototypes. +*/ +int findsfs (char *argv[]); +int dealloc (char *argv[]); +int alloc (char *argv[], int statonly); + +extern int uid_executing (int uid); + + + +int main (int argc, char *argv[]) +{ + int iexit = DV_ERROR; + + if (geteuid()) { + fprintf (stderr, + "Error: uid of $hbin/alloc.e must be set to 0 (root)\n"); + fprintf (stderr, "(rerun install script $hlib/install, or)\n"); + fprintf (stderr, "(login as root: cd $hbin; chown 0 alloc.e)\n"); + exit (DV_ERROR); + } else if (argc < 3) { + fprintf (stderr, "alloc.e called with invalid argument list\n"); + exit (DV_ERROR); + } + + if (strcmp (argv[1], "-a") == 0) + iexit = alloc (&argv[2], 0); + else if (strcmp (argv[1], "-s") == 0) + iexit = alloc (&argv[2], 1); + else + iexit = dealloc (&argv[2]); + + exit (iexit); +} + + +/* ALLOC -- Allocate device with given generic name if its owner is not + * logged in. + */ +int +alloc ( + char *argv[], + int statonly /* if set, just return device status */ +) +{ + register int ruid, mode, i; + register struct file *fp; + struct passwd *getpwuid(); + int rgid; + + if (findsfs (argv) == 0) + return (DV_ERROR); + + if (debug) + printf ("allocate %d files\n", nsfiles); + + for (i=0; i < nsfiles; i++) { + fp = &sfiles[i]; + ruid = fp->f_sbuf.st_uid; + mode = fp->f_sbuf.st_mode; + + /* We don't really care if the uid when the device is not + * allocated is root, bin, or whatever, so long at it is some + * system uid. + */ + if (ruid < 10) + ruid = 0; + + if (ruid == 0 && (mode & 06) != 06) { + if (!statonly) + printf ("rw access to %s is denied\n", fp->f_name); + return (DV_DEVINUSE); + } else if (ruid && uid_executing(ruid)) { + if (ruid != getuid()) { + if (!statonly) + printf ("%s already allocated to %s\n", + fp->f_name, (getpwuid(ruid))->pw_name); + return (DV_DEVINUSE); + } else + return (statonly ? DV_DEVALLOC : XOK); + } + } + + if (statonly) + return (DV_DEVFREE); + + ruid = getuid(); + rgid = getgid(); + + for (i=0; i < nsfiles; i++) { + fp = &sfiles[i]; + if (debug) + printf ("alloc file `%s'\n", fp->f_name); + if (chmod (fp->f_name, RWOWN) == -1) + printf ("cannot chmod `%s'\n", fp->f_name); + if (chown (fp->f_name, ruid, rgid) == -1) + printf ("cannot chown `%s'\n", fp->f_name); + } + + return (XOK); +} + + +/* DEALLOC -- Deallocate device with given generic name if real uid owns all + * sfiles. + */ +int +dealloc (char *argv[]) +{ + register int uid, ruid, i; + register struct file *fp; + + if (findsfs (argv) == 0) + return (DV_ERROR); + + if (debug) + printf ("deallocate %d files\n", nsfiles); + + ruid = getuid(); + if (ruid) + for (i=0; i < nsfiles; i++) { + fp = &sfiles[i]; + uid = fp->f_sbuf.st_uid; + if (uid && uid != ruid) + return (DV_ERROR); + } + + for (i=0; i < nsfiles; i++) { + fp = &sfiles[i]; + if (fp->f_sbuf.st_uid == 0) + continue; + if (debug) + printf ("dealloc file `%s'\n", fp->f_name); + if (chmod (fp->f_name, RWALL) == -1) + printf ("cannot chmod `%s'\n", fp->f_name); + if (chown (fp->f_name, 0, 0) == -1) + printf ("cannot chown `%s'\n", fp->f_name); + } + + return (XOK); +} + + +/* FINDSFS -- Fill in sfiles table with special file names associated with + * device with given generic name. + */ +int +findsfs (char *argv[]) +{ + register struct file *fp; + register char *argp, *ip; + char *fname; + + for (nsfiles=0; (argp = argv[nsfiles]); nsfiles++) { + fp = &sfiles[nsfiles]; + for (ip=fname=argp; *ip; ip++) + if (!isalnum (*ip)) + fname = ip + 1; + if (*fname == '\0') { + printf ("alloc: cannot fstat %s\n", fname); + continue; + } + + sprintf (fp->f_name, "/dev/%s", fname); + if (stat (fp->f_name, &fp->f_sbuf) == -1) { + sprintf (fp->f_name, "/dev/rmt/%s", fname); + if (stat (fp->f_name, &fp->f_sbuf) == -1) { + printf ("alloc: cannot fstat %s\n", fp->f_name); + continue; + } + } + } + + return (nsfiles); +} + + +#ifdef DEFUNCT +/* This is no longer used since we now read the process table instead. */ +/* LOGGEDIN -- Return 1 if uid is logged in, else 0. + */ +int +loggedin (int uid) +{ + register int i; + static int uidcache[10]; + static int nuid = 0; + struct utmpx ubuf; + struct passwd *pw, *getpwuid(); + FILE *ufp; + + for (i=0; i < nuid; i++) + if (uid == uidcache[i]) + return (1); + + if ((ufp = fopen ("/var/run/utmp", "r")) == NULL) { + printf ("cannot open utmp file\n"); + exit (DV_ERROR); + } + + if ((pw = getpwuid (uid)) == NULL) { + printf ("uid %d not in passwd file\n", uid); + exit (DV_ERROR); + } + + do { + if (fread (&ubuf, sizeof (struct utmpx), 1, ufp) == NULL) + return (0); + } while (strncmp (ubuf.ut_user, pw->pw_name, 8) != 0); + + if (nuid < 10) + uidcache[nuid++] = uid; + + return (1); +} +#endif diff --git a/unix/os/dio.c b/unix/os/dio.c new file mode 100644 index 00000000..3da16505 --- /dev/null +++ b/unix/os/dio.c @@ -0,0 +1,9 @@ +/* + * DIO.C -- Stubbed out version of directio for compatibility on systems + * that don't provide this routine in libc.a (e.g., Solaris 5.5). + */ +int +directio (int fd, int advice) +{ + return (-1); +} diff --git a/unix/os/doc/Mach.notes b/unix/os/doc/Mach.notes new file mode 100644 index 00000000..57b433e4 --- /dev/null +++ b/unix/os/doc/Mach.notes @@ -0,0 +1,32 @@ + +MAX_DIGITS +SZ_FNAME +SZ_LINE +SZ_PATHNAME +SZ_USHORT +SZ_type +INDEFt + +twos_complement +byte_swap2 +byte_swap4 +nbits_byte +sz_vmpage +szb_addr +szb_char + +nbits_int +szb_int +base_int +nbase_int +max_int +min_int + +base_real +nbase_real +ndigits_real +minexp_real +maxexp_real +largest_real +smallest_real +epsilon_real diff --git a/unix/os/doc/os.hd b/unix/os/doc/os.hd new file mode 100644 index 00000000..2a4d7c01 --- /dev/null +++ b/unix/os/doc/os.hd @@ -0,0 +1,71 @@ +# Helpdir for the OS package. + +$os = "host$os/" +$doc = "host$os/doc/" + +zawset hlp = doc$zawset.hlp, src = os$zawset.c +zcall hlp = doc$zcall.hlp, src = os$zcall.c +zclcpr hlp = doc$zclcpr.hlp, src = os$zfiopr.c +zcldir hlp = doc$zcldir.hlp, src = os$zgfdir.c +zcldpr hlp = doc$zcldpr.hlp, src = os$zfiopr.c +zdojmp hlp = doc$zsvjmp.hlp, src = os$zsvjmp.c +zfacss hlp = doc$zfacss.hlp, src = os$zfacss.c +zfaloc hlp = doc$zfaloc.hlp, src = os$zfaloc.c +zfchdr hlp = doc$zfchdr.hlp, src = os$zfchdr.c +zfdele hlp = doc$zfdele.hlp, src = os$zfdele.c +zfgcwd hlp = doc$zfgcwd.hlp, src = os$zfgcwd.c +zfinfo hlp = doc$zfinfo.hlp, src = os$zfinfo.c +zfmkcp hlp = doc$zfmkcp.hlp, src = os$zfmkcp.c +zfpath hlp = doc$zfpath.hlp, src = os$zfpath.x +zfprot hlp = doc$zfprot.hlp, src = os$zfprot.c +zfrnam hlp = doc$zfrnam.hlp, src = os$zfrnam.c +zfsubd hlp = doc$zfsubd.hlp, src = os$zfsubd.x +zfxdir hlp = doc$zfxdir.hlp, src = os$zfxdir.x +zgfdir hlp = doc$zgfdir.hlp, src = os$zgfdir.c +zgtime hlp = doc$zgtime.hlp, src = os$zgtime.c +zgtpid hlp = doc$zgtpid.hlp, src = os$zgtpid.c +zintpr hlp = doc$zintpr.hlp, src = os$zintpr.c +zlocpr hlp = doc$zlocpr.hlp, src = os$zlocpr.c +zlocva hlp = doc$zlocva.hlp, src = os$zlocva.c +zmain hlp = doc$zmain.hlp, src = os$zmain.c +zmaloc hlp = doc$zmaloc.hlp, src = os$zmaloc.c +zmfree hlp = doc$zmfree.hlp, src = os$zmfree.c +zopcpr hlp = doc$zopcpr.hlp, src = os$zfiopr.c +zopdir hlp = doc$zopdir.hlp, src = os$zopdir.c +zopdpr hlp = doc$zopdpr.hlp, src = os$zfiopr.c +zoscmd hlp = doc$zoscmd.hlp, src = os$zoscmd.c +zpanic hlp = doc$zpanic.hlp, src = os$zpanic.c +zraloc hlp = doc$zraloc.hlp, src = os$zraloc.c +zsvjmp hlp = doc$zsvjmp.hlp, src = os$zsvjmp.c +ztslee hlp = doc$ztslee.hlp, src = os$ztslee.c +zxgmes hlp = doc$zxgmes.hlp, src = os$zxgmes.c +zxwhen hlp = doc$zxwhen.hlp, src = os$zxwhen.c + +zfiobf hlp = doc$zfiobf.hlp, src = os$zfiobf.c +zfiotx hlp = doc$zfiotx.hlp, src = os$zfiotx.c +zfioty hlp = doc$zfioty.hlp, src = os$zfioty.c +zfiolp hlp = doc$zfiolp.hlp, src = os$zfiolp.c +zfiopr hlp = doc$zfiopr.hlp, src = os$zfiopr.c +zfiosf hlp = doc$zfiosf.hlp, src = os$zfiosf.c +zfiomt hlp = doc$zfiomt.hlp, src = os$zfiomt.c + +zopntx hlp = doc$zopntx.hlp, src = os$zfiotx.c +zclstx hlp = doc$zclstx.hlp, src = os$zfiotx.c +zgettx hlp = doc$zgettx.hlp, src = os$zfiotx.c +zputtx hlp = doc$zputtx.hlp, src = os$zfiotx.c +zflstx hlp = doc$zflstx.hlp, src = os$zfiotx.c +zsektx hlp = doc$zsektx.hlp, src = os$zfiotx.c +znottx hlp = doc$znottx.hlp, src = os$zfiotx.c +zstttx hlp = doc$zstttx.hlp, src = os$zfiotx.c +zopnbf hlp = doc$zopnbf.hlp, src = os$zfiobf.c +zclsbf hlp = doc$zclsbf.hlp, src = os$zfiobf.c +zardbf hlp = doc$zardbf.hlp, src = os$zfiobf.c +zawrbf hlp = doc$zawrbf.hlp, src = os$zfiobf.c +zawtbf hlp = doc$zawtbf.hlp, src = os$zfiobf.c +zsttbf hlp = doc$zsttbf.hlp, src = os$zfiobf.c +zzopmt hlp = doc$zzopmt.hlp, src = os$zfiomt.c +zzclmt hlp = doc$zzclmt.hlp, src = os$zfiomt.c +zzrdmt hlp = doc$zzrdmt.hlp, src = os$zfiomt.c +zzwrmt hlp = doc$zzwrmt.hlp, src = os$zfiomt.c +zzwtmt hlp = doc$zzwtmt.hlp, src = os$zfiomt.c +zzrwmt hlp = doc$zzrwmt.hlp, src = os$zfiomt.c diff --git a/unix/os/doc/os.ms b/unix/os/doc/os.ms new file mode 100644 index 00000000..61aa14fe --- /dev/null +++ b/unix/os/doc/os.ms @@ -0,0 +1,4249 @@ +.RP +.ND +.TL +A Reference Manual +.br +for the +.br +IRAF System Interface +.AU +Doug Tody +.AI +.K2 "" "" "*" +May 1984 +.AB +The IRAF system interface is the interface between the transportable IRAF +system and the host operating system. An overview of the software design +of the IRAF system is presented, naming the major interfaces and +discussing their relationships. The system interface is shown to consist +of a language interface, the subset preprocessor (SPP), and a procedural +interface, the IRAF kernel. The reasoning which led to the choice of +a Fortran preprocessor for the language interface is reviewed. A reference +manual for the IRAF kernel is presented, followed by the detailed technical +specifications for the kernel procedures. +.AE + +.NH +Introduction +.PP +The IRAF system libraries currently total 42 thousand lines of code. +The applications software adds another 75 thousand lines +of code, about half of which was imported (i.e., the graphics utilities and +math library routines). The UNIX implementation of the machine dependent +portion of the IRAF system consists of some 3300 lines of code, or about +3 percent of the current system. The remainder of the system, i.e., +approximately 97 percent of the current system, is machine and device +independent. It is this 3 percent of the IRAF system which is machine +dependent, known as the \fBsystem interface\fR, which is the focus of this +document. +.PP +The importance of maximizing the transportability of a large software system +cannot be overemphasized. The IRAF system is required to run on +a variety of different computers and operating systems from the time of +its first release to the end of its useful life. The computer the IRAF +system is being developed on is already old technology. Two years from +now when IRAF is a mature system, it will almost certainly contain 20 to +30 manyears of software. With the increasing dependence on computers for +scientific data analysis, and the demand for increasingly powerful software, +it is no longer possible to keep up with demand if we have to throw out +our systems every 5 or 10 years and start over. +.PP +Commercially developed operating systems/programming environments such as +UNIX and ADA offer some hope for the future. At present, however, ADA is +not widely available and there are at least half a dozen versions of UNIX +in use, with no clear cut standard yet to emerge. The different versions +of UNIX resemble each other, but there are many differences. UNIX has been +steadily evolving for ten years and there is no reason to expect that the +process will stop now. +.PP +Many manufacturers offer machine dependent extensions to get around the +inefficiencies of basic UNIX, and it is desirable to be able to take advantage +of these in a production system. Even in a hardcore UNIX system like 4.2BSD, +significant efficiency gains are possible by taking advantage of the +peculiarities of the 4.2BSD kernel, e.g., by always doing file transfers +in units of the machine page size, into buffers aligned on page boundaries. +In a large system it is difficult to take advantage of such machine +peculiarities unless the system has a well defined and well isolated interface +to the host system. There is no denying the fact that despite the many +attractive aspects of UNIX, it is nice to have the option of switching to +a more efficient operating system if IRAF is to be used in a production +environment. +.PP +Perhaps the most powerful argument is that despite the increasingly widespread +use of UNIX, many of the sites for which IRAF is targeted do not or can not +run UNIX on their current computers. The increasing availability of +transportable operating systems will make transporting IRAF easier, but is no +substitute for a well defined and well isolated system interface. +.PP +The \fBsystem interface\fR is the interface between the machine independent +IRAF software and the host operating system. +The system interface consists of a procedural interface, the IRAF +\fBkernel\fR, and a language interface, the IRAF Subset Preprocessor +language (SPP). Both types of interface are required to isolate the +IRAF software from the host system. +.PP +We first present an overview of the structure of the IRAF system, naming the +major interfaces and explaining their functions. The system interface is +introduced and the role it plays in the full system is described. +The choice of an implementation language for IRAF is next discussed, +concentrating on the role the language interface plays in addressing the +transportability problem. Next we define the kernel and discuss its attributes. +The conceptual model of the host operating system, or \fBvirtual machine +model\fR assumed by IRAF is presented. The design and functioning of the +kernel is discussed in detail, followed by the detailed specifications +(\fBmanual pages\fR) for the subroutines constituting the actual interface. +.PP +It is not necessary to carefully read the first part of this document +to implement the IRAF system interface for a new host operating system. +The first part of this document (the \fBreference manual\fR) concentrates on +the role the system interface plays in IRAF, the principles underlying +the design of the interface, and on how and why the interface came to be +what it is. The second part of the document (the \fBmanual pages\fR) +presents the detailed specifications for the individual routines comprising +the system interface, and is intended to be self contained. The reference +manual tells what modules the interface consists of, how they work together, +and why they are designed the way they are. The manual pages tell +precisely \fIwhat\fR each routine does, with no attempt to explain why, +or how the routine fits into the system. +.PP +Interfacing to new \fBgraphics devices\fR is likely to be one of the most +difficult problems to be solved in porting the IRAF system. Nonetheless +the graphics device interface is not (or should not be) part of the system +interface, and is not discussed here. Ideally, a graphics device will be +interfaced to IRAF file i/o as a binary file. The device should +have a data driven interface, rather than a control interface, i.e., +all device control should be effected by inserting control instructions in +the data stream transmitted to the device, rather than by calling system +dependent subroutines. The software required to drive the device should +be device independent, table driven, and fully portable. Virtually all +graphics devices either fit this model, or can be made to fit this model +by writing system dependent device drivers to interpret metacode generated +by the high level, machine and device independent software. + +.NH +Structure of the IRAF System Software +.PP +The major components of the IRAF system are +the high level applications and systems \fBprograms\fR and \fBpackages\fR, +the Command Language (CL), also known as the \fBuser interface\fR, +the \fBprogram interface\fR (library procedures called by programs), +and the \fBsystem interface\fR. The system interface is further subdivided +into the \fBkernel\fR and the \fBlanguage interface\fR. +Other system modules not relevant to our discussion are the math library +and the graphics device interfaces. +.PP +The relationship and relative importance of these modules is strongly +dependent upon one's point of view; all points of view are equally valid. +From the point of view of the (highly idealized) user, looking at the +system from the top level, the user is at the center of the system and +is in command. The CL appears to be the central piece of software, and +applications packages are mere extensions of the CL. To the extent that the +user is aware of the host system, the CL appears to be the interface +between the user and the host system. The user expects the system to behave +predictably and reliably, and be responsive to their commands. +To a first approximation, the user does not care what language programs +are written in, or how they are interfaced to the host system (real users, +of course, are often programmers too, and do care). +.PP +From the point of view of the applications programmer, the program they +are writing is at the center of the system and is in command (and indeed +the program does control the system when it is run). The programmer sees +only the abstract problem to be solved and the environment in which the +solution must be constructed, defined by the program interface and the SPP +language. The CL is an abstract data structure, accessed via the CLIO +library in the program interface. The fact that there is a host system +underlying the program interface is irrelevant, and is of no concern to the +applications programmer. As far as the applications programmer is concerned, +the CL could be CL1, CL2, a file, the host OS command interpreter, Forth, +Lisp, or anything else. Ideally, the applications programmer does not know +that the target language of the SPP is Fortran. +.PP +From the point of view of the systems programmer, the kernel is the center +of the system, with the host operating system below and the program interface +above. The system software is subservient to the program which calls it, +and does exactly what it is told to do and nothing more. The CL and the SPP +compiler are \fIapplications programs\fR which use only the facilities +of the program and system interfaces. +.PP +The structural design of the IRAF software is outlined in the figure below. +In general, control and parameters flow downward and data flows both both +downward and upward (mostly upward). The procedures at one level do +not call procedures at a higher level, e.g., kernel routines are +not permitted to call procedures in the program interface libraries. +A procedure may call other procedures at the same level or in the next +lower level, but calls to routines more than one level lower are avoided, +i.e., a program should not bypass the program interface and talk directly +to the kernel. IRAF applications programs \fInever\fR bypass the system +interface to talk directly to the host system. + +.KS +.ce +\fBStructure of the Major IRAF Interfaces\fR +.sp +.nf + \fIuser interface\fR + high level program + \fIprogram interface\fR + \fIsystem interface\fR + host operating system +.fi +.KE + +.PP +This structure chart illustrates only the control hierarchy of the major +interfaces. Additional structure is imposed on the actual system. +Thus, the CL uses only a small portion of the facilities provided by the +program interface, calling external programs to perform most functions. +Few system or applications programs, on the other hand, use the process +control facilities, the part of the program interface most heavily used +by the CL (apart from file i/o). The CL and external applications +programs are minimally coupled, using only human readable text files for +interprocess communication (interprocess communication is implemented as +a special file in IRAF). +.PP +These restrictions tend to result in more functional programs which can be +combined in many ways at the CL level, and which can be used quite +productively without the CL. In particular, any IRAF program can easily +be debugged without the CL, using the host system debugger, and any IRAF +program can be used productively on a system which cannot support the CL. +The system perceived by the user at the CL level can easily be extended +or modified by the user or by a programmer. +.PP +The \fBprocess structure\fR of the IRAF system, with the CL serving +up and networking all processes, while fundamental to the design of +the IRAF system, is not relevant to a discussion of the system interface +because it is all handled above the system interface, i.e., in the machine +independent code. Many quite different process structures are possible +using the one IRAF system interface. Concentration of most or all of the +complex logic required to implement process control, the CL process cache, +the CL/program interface, pseudofiles, multiprocessing, i/o redirection, +exception handling and error recovery, efficient file access, etc., +into the machine \fIindependent\fR code was a major goal of the IRAF +system design. + +.NH +The IRAF System Interface +.PP +As has already been noted, the IRAF system interface consists of both a +procedural interface or kernel (library of Fortran callable subroutines), +and a language interface (preprocessor for Fortran). All communication +with the host system is routed through the kernel, minimizing the machine +dependence of the system and providing scope for machine dependent +optimizations. Similarly, all code other than existing, imported numerical +Fortran procedures is processed through the language interface, further +minimizing the machine dependence of the system. The language interface, +or subset preprocessor (SPP), isolates IRAF programs from the peculiarities +of the host Fortran compiler, provides scope for optimization by making it +possible to take advantage of the nonstandard features of the compiler +without compromising transportability, and does much to correct for the +defects of Fortran as a programming language. +.NH 2 +The Language Interface +.PP +The kernel alone is not sufficient to solve all the problems of +transporting a large software system. There remain many problems associated +with the programming language itself. The many problems of transporting +even purely numerical software are well known and we will not attempt to +discuss them in detail here. The reasoning which led to the decision to +implement the IRAF system in a Fortran preprocessor language (SPP) is less +obvious, and is probably worth recounting. In the process of retracing +the logic which led to the decision to develop SPP, we will come to understand +the purpose of the language interface. +.PP +For some reason programming languages are one of the most controversial +topics in all of programming. No language is perfect, and it is important +to try to objectively gauge the advantages and disadvantages of a language +for a particular application. It is difficult to make such comparisons +without the injection of opinion, and for that we apologize. In the final +analysis the choice of a language is probably not all that important, +provided the cost of the project is minimized and the resultant code is +reliable, portable, readable, and efficient. Only the Fortran and C languages +are discussed; these were the only candidates seriously considered in 1982, +when the decision to implement the IRAF system in a Fortran preprocessor +language was made. +.NH 3 +Fortran +.PP +Consider the typical scientific applications program. Such a program may +need to talk to the CL, access files, access images, access databases, +dynamically allocate memory, generate graphics, perform vector operations, +and so on. These are all functions provided by the program interface. +In addition, in a scientific program there will often be some central +transformation or numerical computation which will almost certainly be +performed by a Fortran subprogram. There is an enormous amount of high +quality Fortran numerical and graphics software available, both commercially +and in the public domain, which IRAF programs must have access to. A third +of the current IRAF system consists of highly portable, numerical (no i/o) +Fortran code, all of it in the public domain. +.PP +To be useful, these general purpose, numerical Fortran subroutines must +be integrated into an IRAF program to perform some specific function. +Here we run into the first serious problem: while Fortran is great for +numerical procedures, it has many defects as a general purpose programming +language. When it comes to complex systems software, Fortran is vastly +inferior to a modern programming language such as C or Pascal. It is +very difficult to implement complex nonnumerical applications in +standard Fortran; the temptation to use a manufacturer's nonstandard +language extensions is often too difficult to resist, and a nonportable +program results. Clearly, Fortran is not the language of choice for the +IRAF system software, in particular the program interface. +.PP +The next question is what to do about the high level part of the scientific +applications program, the part which talks to the program interface, +performs applications specific functions, and eventually calls the numerical +Fortran procedures. In a large scientific package Fortran subprograms +will almost certainly be used somewhere in the package, sometimes quite +heavily, but the bulk of the software is often very similar to systems +software, concerned with allocating resources, managing data structures, +doing i/o of various kinds, and directing the flow of control. +.PP +This is all nonnumerical programming, for which Fortran is poorly suited. +Many Fortran compilers provide nonstandard language extensions which make +it easier to code nonnumerical applications in Fortran. Most applications +programmers and scientists are not intimately familiar with the Fortran +standard, and are more interested in getting a program working than in making +it portable, and nonportable code will result. In any case, programmers +and scientists should not have to struggle to code an application in +a language which was designed for another purpose. We conclude that Fortran +is not the language of choice for general scientific programming within a +complex system. +.PP +A more serious problem with using Fortran for general scientific programming, +however, is that the high level portion of an IRAF scientific program depends +heavily on the capabilities of the program interface. To produce sophisticated +scientific programs with minimum effort we must have a large and powerful +program interface, providing the capabilities most often needed by scientific +programs. We also need a large and powerful program interface for +\fIsystem\fR programs, and in fact the capabilities required by scientific +programs are not all that different than those of system programs, so +clearly it is desirable if the same program interface can support both +kinds of programs. +.PP +The next step is to explore the implications of the heavy dependence of +a systems or scientific program on the program interface. The program +interface is a large interface, consisting of a dozen subsystems containing +several hundred procedures. To have a sophisticated, high level, efficient +interface, it is necessary to use "include" files to parameterize argument +lists, machine parameters, and data structures, we must use dynamic memory +management (pointers) for buffer allocation, and something has to be done +about error handling and recovery. In short, there is a lot of communication +between high level programs and the program interface. This level of +communication is only feasible if the program interface and the high level +program are written in the \fIsame language\fR. Standard Fortran provides +\fIalmost no language support\fR for these facilities. +.sp +.KS +.LP +To summarize our reasoning to this point: +.RS +.IP \(bu +Fortran must be used extensively in the scientific applications. +.IP \(bu +Fortran is not the language of choice for IRAF systems software, +in particular the program interface. +.IP \(bu +Fortran is not the language of choice for general scientific programming, +because most scientific programming is nonnumerical in nature, i.e., +much like systems programming. +.IP \(bu +A single program interface should support both systems programs and +scientific programs. +.IP \(bu +The level of communication required between a high level program and the +program interface requires that both be written in the same language. +.IP \(bu +Standard Fortran provides almost no language support for include files, +globally defined parameters, dynamic memory management and pointers, +data structures, or error recovery. These facilities are required by +both systems and applications software. +.RE +.KE +.NH 3 +Mixing C and Fortran in the same System +.PP +All of our logic to this point forces us to conclude that standard Fortran +just is not suitable for the bulk of the IRAF software. The complexity of +large applications packages, not to mention that of the system software, +would be unmanageable in straight Fortran. Nonetheless we must +still face the requirement that a third or so of the system be existing, +imported numerical Fortran procedures. The obvious question to be answered +is, if Fortran is such a poor choice for the main programming language +of IRAF, can we program in an appropriate modern structured language like C, +calling the numerical Fortran functions and subroutines from within C programs? +.PP +The answer is sure we can, if our target system supports both a Fortran +compiler and a C compiler, but there are serious portability implications. +Furthermore, when examined closely C turns out to be not that great a +language for general scientific programming either, and the defects of C +cannot easily be fixed, whereas those of Fortran can. +.PP +Mixing two different languages in the same program is straightforward on +many operating systems and possible with difficulty on others. No language +standard could attempt to specify how its subprograms would by called by a +completely different, unspecified language, so the one thing we can be +sure of is that the method used will be system dependent. Even on systems +where mixing subprograms from different languages is straightforward, +there are many potential problems. +.PP +Argument lists present many problems. +A potentially deadly problem is the fact that C is a recursive +language while (standard) Fortran is not. C expects the arguments to a +procedure to be passed on the stack, while Fortran was designed with static +storage of argument lists in mind; static storage is somewhat more efficient. +Arguments pushed on a stack are usually pushed in the reverse of the order +used for static allocation. C is call by value; Fortran is call by +reference. Returning a function value is trivial on many systems, +but can be a problem. The Fortran standard requires that a function be +called as a function (in an expression) and not as a subroutine, while +C permits either type of call (all C procedures are functions). +.PP +The method used to implement Fortran character strings is machine dependent; +some machines may pass two items in the argument list to represent a +character string, while others pass only one. On some machines a Fortran +character string is implemented with a count byte, on others an end of string +marker is used. The C standard requires that a character string be delimited +by a trailing zero byte. Thus, on some systems C and Fortran character strings +will be equivalent; programs written on such a system are not portable +to to systems where strings are implemented differently in the two languages. +.PP +Global variables are likely to be a problem, because Fortran common blocks +and C external variables are quite different types of data structures. +Though it would be poor programming practice to use global variables +or common blocks to pass data between C and Fortran procedures, it is +sometimes justified for control parameters, in particular in connection +with error handling. A C include file cannot be accessed from a Fortran +program. +.PP +Finally, external identifiers have a set of problems all of their own. +On some systems, e.g. VMS and AOS, C and Fortran external identifiers are +equivalent, and a procedure is referred to by the same name in both +languages. This makes it easy to mix calls in the two languages, +but can lead to serious name conflicts in libraries. On other systems, +e.g. UNIX, the external identifiers generated for the two languages are +\fInot\fR equivalent, and a C procedure cannot be called from Fortran +unless special provisions are taken (the UNIX Fortran compiler adds an +underscore to all Fortran external identifiers). +.PP +The problem is not that it is hard to mix the two languages, but that +every one of the points mentioned above is a potential machine dependency +which is not covered by any standard. We conclude that mixing C and +Fortran in the same program is inevitably going to be machine dependent. +The problem is controllable only if the number of procedures common to +the two languages is small, or if some automated technique can be developed +for interfacing the two languages. The former solution is straightforward, +the second requires use of some form of \fIpreprocessor\fR to isolate the +machine dependencies. +.PP +C and Fortran should not be mixed in applications software because the +number of Fortran procedures involved is potentially very large. +Since the number of procedures is large, the effort required to port the +applications is also potentially large, and it is the applications which +change most between system releases. If the applications total, say, +200 thousand lines of code, a new release of the system occurs every 3 months, +and it takes two weeks to make all the changes in the high level software +necessary to port it, then we have a bad situation. Porting the system +should ideally be a simple matter of reading a tape, possibly modifying a +config file or two, and running diagnostics. +.PP +C and Fortran should not be mixed in the program interface (in the system +libraries) because it introduces machine dependency into the program +interface where formerly there was none. Less obviously, the problem +discussed in \(sc3.1.2 of communication between modules written in different +languages is very serious. The modules of the program interface use +global \fBinclude\fR files to communicate with one another, and to +parameterized the characteristics of the host system. To ensure a reliable +and modifiable system, there must be only one copy of these include files +in the system. Furthermore, the IRAF error handling scheme, employed +in all code above the kernel, is based on the use of a Fortran common +and to access this from C code would be awkward and would introduce additional +machine dependence. +.PP +The only remaining alternative is to write applications programs entirely in +Fortran and the system software in C, using a small program interface between +the two parts of the system. The problems with this approach were noted in +the last section. The small program interface will inevitably prove too +restrictive, and more and more scientific programs will be written as +"system" programs. These programs will inevitably want to use the numerical +Fortran libraries, and the problem of a large interface between two languages +resurfaces at a different level in the system. +.NH 3 +Critique of C as a Scientific Language +.PP +The major strengths of C as a programming language are that it lends itself +well to structured, self documenting programming, has great expressive power, +strong compile time type checking, tends to result in highly transportable +code (when not mixed with other languages), and is efficient for a large +range of applications. C has been widely used in computer science research +for a decade, and many high quality systems applications are available in +the public domain. +.PP +As a scientific programming language, however, C has serious shortcomings; +C was not designed to be a scientific programming language. The major problem +with C as a scientific programming language is that the scientific community +already has such a large investment in Fortran, and it is difficult to mix +the two languages, as we have already discussed. Upon close analysis we find +that there are additional problems, and these deserve mention. +.PP +C does not support multidimensional arrays. C provides something similar +using pointers, but the feature is really just a side effect of the +generality of pointers, and is not fully supported by the language. +A multidimensional array cannot be passed to a subprogram along with its +dimensions as it can in Fortran. Few C compilers optimize loops, i.e., +evaluate common subexpressions (such as array subscripts) only once, +or remove constant expressions from inner loops. These problems can +be overcome by sophisticated use of pointers, but such hand optimization +of code is extra work and increases the complexity of the code. +On a machine such as the Cray, which has a Fortran compiler that +recognizes vector operations, the problem would be even more severe. +.PP +Char and short integer (8 and 16 bit) expressions are evaluated using +integer instructions (32 bits on a VAX), and single precision floating +expressions are evaluated using double precision instructions. +In a tight loop, this will require the addition of a type conversion +instruction to promote a variable to the higher precision datatype, +and another to convert back to the original datatype after the evaluation. +This alone can double the size and execution +time of a tight loop. In addition, on many machines double precision +floating is not well supported by the hardware and is several times more +expensive than single precision floating. C does not support the +\fBcomplex\fR datatype, important in some scientific applications. +.PP +The C language does not include support for \fBintrinsic\fR and \fBgeneric\fR +functions. These are used heavily in scientific applications. Typed function +calls are required to access the scientific functions, and to perform +exponentiation. I am not aware of any C standard for the scientific functions, +though most systems appear to have adopted the Fortran standard. +The scientific functions, e.g., the trigonometric functions, are evaluated +in double precision. This could lead to a serious degradation +of performance in a large class of applications. +.PP +Despite these quite serious shortcomings, faced with the task of coding a +large and complex application I would prefer C to Fortran, if I had only +the two languages to choose from. Fortunately there is a third alternative, +the use of a Fortran preprocessor. +This approach preserves the best features of Fortran while providing many +of the nice features of a modern language such as C, and in addition allows +us to provide language level support for the IRAF i/o facilities. +The preprocessor approach provides a means of isolating both systems and +applications code from the underlying host compiler, making portability +a realistic goal. +.NH 3 +The IRAF Subset Preprocessor Language +.PP +The Subset Preprocessor language (SPP) is a precursor to a full language +scheduled for development in 1986. The subset language is a fully defined, +self contained language, suitable both for general programming and for +numerical scientific programming. The basic language is modeled after +both C and Ratfor but is a distinct language and should not be confused +with either. SPP is fully integrated into the IRAF system, i.e., +SPP provides substantial language support for the program interface, +the IRAF system itself is written in SPP, and the SPP compiler is an IRAF +applications program written in SPP and using the facilities provided by +the program interface (this is not true of the original preprocessor but +that is not relevant to the design). The syntax of the SPP language is +nearly identical to that of the IRAF command language. +.PP +The SPP language is defined in the document \fIA Reference Manual for the +IRAF Subset Preprocessor Language\fR. The language provides modern +control flow constructs, a wide range of datatypes, support for both system +and user \fBinclude\fR files, a macro definition facility, free format +input, long (readable) identifiers, C-like character constants and strings, +Fortran-like arrays, access to the standard Fortran intrinsic and generic +functions, powerful error handling facilities, and limited but adequate +support for pointers, automatic storage allocation, and data structuring. +Since the target language is Fortran, there is no problem calling Fortran +subprograms from SPP programs or vice versa. We do require, however, +that the Fortran subprograms be purely numerical in nature, i.e., +no Fortran i/o is permitted. +.PP +The function of the preprocessor is to translate an SPP source file +into a highly portable subset of ANSI-66 Fortran. The transformation is +governed by a set of machine dependent tables describing the characteristics +of the host computer and of the target Fortran compiler. These tables +must be edited to port the preprocessor to a new machine; the tables are +ideally the only part of the preprocessor which is machine dependent. +.PP +Even if it should turn out that all of the necessary machine dependence +has not been concentrated into the tables, however, it will often be possible +to port the hundreds of thousands of lines of code in the system by +modifying or adding a few lines of code to the preprocessor, +\fIbecause we have placed an interface between the language of the IRAF system +and that of the host computer\fR. The language interface provides both +a solution to the problem of transporting software between different +contemporary machines, and protection from future changes in the Fortran +language. +.PP +The principal motivation of the preprocessor approach taken in IRAF is +that it provides a real solution to the transportability problem, i.e., +one which does not depend upon perfect programmers. An additional incentive +is that by defining our own language we can provide excellent support the +IRAF i/o facilities, i.e., they can be more than just subroutines and +functions. +.PP +If one has the freedom of being able to modify the programming language +used by applications programs, one can do things that are impractical to +do any other way. In other words, it becomes feasible to solve problems +that were formerly too difficult to address. An example is the use of +lookup tables to implement blocked storage of images, an alternative to +line storage mode which is superior in a large class of image processing +applications. To do this well requires language support, and it is unlikely +that such support will be found in any standard, general purpose programming +language. The SPP is intended partially to provide a path for the future +development of the IRAF system, in the hope that the system will be able +to evolve and be competitive with new systems in coming years. +.NH 3 +Limitations of the Subset Preprocessor +.PP +No compiled language, SPP included, can guarantee transportability. +SPP programs developed on one machine will normally have to tested on +one or more other machines before they can be declared portable. +SPP suffers from many of the same portability problems as standard +Fortran, the main difference being that the output Fortran is very +simple, and nonstandard language extensions are strictly controlled. +Further discussion of the portability aspects of SPP programs is given +in the document \fIIRAF Standards and Conventions\fR. +.PP +We hope to eventually have IRAF running on several types of machines +at the development centers. New releases will be brought up and tested +on several different machines before distribution. +No large software package developed on a single system is portable; +if it is tested on even two different systems, that is much better. +.PP +The SPP language also depends on certain side effects which are not +specified in the Fortran standard, but which many other systems also +depend upon and which are commonly permitted by Fortran compilers. +These include: +.RS +.IP [1] +It must be possible to reference beyond the bounds of an array. +.IP [2] +It must be possible to reference a subarray in a call to a subprocedure, +i.e., "call copyarray (a[i], b, npix)". +.IP [3] +The compiler should permit a procedure to be called with an actual +argument of type different than that of the corresponding dummy argument +(except type \fBcharacter\fR: SPP does not use this type anywhere). +.IP [4] +It must be possible to store a machine address or the entry point +address of an external procedure in an integer variable. +.RE +.LP +Common language extensions used by the preprocessor in output code include +the nonstandard datatypes such as INTEGER*2, any associated type coercion +functions (INT2), and the boolean intrinsic functions if provided by the +target compiler. +.PP +The output of the current preprocessor is ANSI-66 Fortran only to a first +approximation. The following features of Fortran 77 are also used: +.DS +general array subscripts +zero-trip do loop checking +\fBsave\fR statement +\fBentry\fR statement +generic intrinsic functions (\fBmax\fR rather than \fBamax0\fR, etc.) +.DE +All of these extensions are correctable in the preprocessor itself +except the use of the \fBentry\fR statement, if it should ever prove +necessary. + +.NH 2 +Bootstrapping the System +.PP +Since the SPP is fully integrated into the system -- the program interface +is written in SPP and SPP uses the program interface, one may have been +wondering how to go about getting it all set up in the first place. +The basic procedure for porting IRAF to a new system is as follows. +The actual procedure has not yet been fully defined and will be tailored +to the individual target systems, i.e., there will be a separate +installation guide and distribution package for UNIX, VMS, DG/AOS, +and any other systems supported by the IRAF development team. +.RS +.IP [1] +Implement and test the kernel routines. +.IP [2] +Compile the bootstrap SPP, which has already been preprocessed for the +target machine (the datatype used to implement \fBchar\fR must match that +expected by the kernel procedures). +.IP [3] +Edit the system dependent files \fBiraf.h\fR, \fBconfig.h\fR, and the +preprocessor tables to define the characteristics of the host machine. +.IP [4] +Preprocess and compile the production SPP. +.IP [5] +Do a full \fBsysgen\fR of the system libraries (the program interface etc.). +This takes a couple hours on the VAX/UNIX 11/750 development system; get +the config tables right the first time. +.IP [6] +Run diagnostics on the system library procedures. +.IP [7] +\fBMake\fR the applications packages. +.RE +.PP +Once the system has been ported, installing a new release requires only +steps 5 through 7 (sometimes step 4 may also be required), after reading +the distribution tape. + +.NH 2 +The IRAF Kernel +.PP +The IRAF kernel is a set of Fortran callable subroutines. +Every effort has been made to make these primitives as simple as possible; +the kernel primitives provide raw functionality with minimum overhead. +Ideally the kernel primitives should map directly to the kernel of the +host operating system. The implementation of the kernel primitives +should emphasize simplicity and efficiency; these primitives embody the +machine dependence of IRAF and there is little reason to try to make them +machine independent. Critical routines should be coded in assembler +if a substantial gain in efficiency will result. In IRAF, \fIall\fR +communication with the host system is routed through the kernel, +so kernel efficiency is paramount. +.PP +With very few exceptions, applications programs and high level systems +modules do not talk directly to the kernel. The kernel primitives are +called only by the routines comprising the core of the IRAF \fBprogram +interface\fR, the lowest level of the machine independent part of the +IRAF system. The core of the program interface, i.e, file i/o, process +control, exception handling, memory management, etc., combined with the +kernel, constitute a \fBvirtual operating system\fR, the heart of IRAF. +The virtual operating system approach is the key to maximizing +transportability without sacrificing either functionality or efficiency. +.PP +Ideally all of the machine dependence of the IRAF system is concentrated +into the kernel, which should be as small and efficient as possible while +offering sufficient raw functionality to support a large and sophisticated +system. In practice it is possible to come quite close to this ideal, +although the range of host systems on which the kernel can be implemented is +finite, being inversely proportional to the richness of function +provided by the kernel. We did not consider it acceptable to provide +transportability at the expense of a restrictive and limited program +interface, so IRAF has a large and quite sophisticated program interface +which depends upon a sizable kernel. The IRAF kernel embodies much of +the functionality provided by the typical minicomputer operating system, +and should be implementable on a wide range of such systems. +.NH 2 +The Virtual Machine Model +.PP +The virtual machine model is the conceptual model of the host machine assumed +by the kernel. The difficulty of implementing the kernel on a given host +depends on how closely the model matches the host operating system. In general, +the older large batch oriented machines do not match the model well, +and it will be difficult to port the full IRAF system to such a machine. +At the other end of the scale are the small 16 bit minicomputers; these +machines do not have a sufficiently large memory addressing range to run IRAF. +In the middle are the multiprocessing, multiuser, terminal oriented supermicro +and minicomputers with large address spaces and large physical memories: +these are the machines for which the IRAF system was primarily designed. +.PP +Our intent in this section is to summarize the most important features of +the virtual machine model. Much of the material given +here will be presented again in more detail in later sections. The design +of the IRAF system is such that there are actually two distinct virtual +machine models, the full model and a subset model. The subset model assumes +little more than disk file i/o, and will be presented first. +.NH 3 +The Minimal Host Machine +.PP +Even though it may be difficult to run the \fIfull\fR IRAF system on a large +batch oriented (timesharing) machine, it should still be possible to run most +of the science software on such a system. The IRAF system was designed such +that the science software is placed in separate processes which can be run +independently of the CL and of each other. +These processes actually use only a portion of +the full system interface; most of the system and device dependence is in +the CL and the graphics control processes, i.e., in the user interface, +which is what one has to give up on a batch machine. +.PP +On the typical batch oriented timesharing system, the IRAF applications +programs would be run under control of the host job control language, +reading commands and parameters from an input file, and spooling all +textual output into an output file. The IRAF command language would not +be used at all; this mode of operation is built into the present system. +Little more than file i/o is required to run a program on such a system, +though dynamic memory allocation is highly desirable. Exception handling +and error recovery can be done without if necessary; process control is not +used by applications processes. The i/o subsystems required by an +applications program are CL i/o, image i/o, and database i/o, each of +which is built upon file i/o. +.PP +Applications programs that produce graphics write device independent +metacode instructions, rather than talking directly to a graphics device, +so a graphics device interface is not required to run an applications +program. Graphics output can be discarded, or the output file can be +postprocessed to generate graphics hardcopy. +Graphics input (cursor readback) is parameterized, so any program that +normally reads a cursor can just as easily read coordinates directly from +the input file. +.PP +All of the software in the science modules is Fortran or Fortran based +(C is used only in the CL and in some kernels), so only a Fortran compiler +is required. A C compiler is currently required to compile the command +language, though it is not necessary to have such a compiler on every +physical machine. We can compile the CL on one machine and distribute +the object code or executables to other machines of the same type; this +will be done, for example, for VAX/VMS. +.PP +If it is necessary to port IRAF to a machine which +does not have a C compiler, it is feasible to code a basic, stripped down +command language in SPP in a few weeks. A command language capable of +executing external processes, parsing argument lists, managing parameter +files, and redirecting i/o is sufficient to run most of the IRAF software, +the main loss being CL scripts. Virtually all of the system and science +software is external to the CL and would be unaffected by substitution of +a new CL. +.NH 3 +The Ideal Host Machine +.PP +The minimal IRAF target machine therefore need offer little more than +file i/o and a Fortran compiler. One would have to do without a nice user +interface, but it should still be possible to do science in the fashion it +has traditionally been done on such machines. +Fortunately, however, minicomputers of modern design are widely available today +and will be increasingly available in the future. We expect that most IRAF +implementations will be ports of the full system onto a modern supermicro or +minicomputer. Such a host system should provide the following general classes +of facilities: + +.DS +.RS +.IP \(bu +file i/o, file management +.IP \(bu +process control +.IP \(bu +exception handling +.IP \(bu +memory management +.IP \(bu +date and time +.IP \(bu +bit and byte primitives +.RE +.DE + +Most of the complexity of the kernel is in file i/o, process control, +and exception handling. File management (file deletion, renaming, etc.) +is straightforward on virtually any system. Memory management can be +some work to implement, but many systems provide dynamic allocation +primitives in which case the interface is trivial. The date and time +facilities assume that the host provides some sort of facilities for +reading the clock time (ideally as a high precision integer) and the cpu +time consumed by a process. The bit and byte primitives do not actually +do any i/o, and are included in the interface primarily because Fortran +is difficult to use for such applications. +.PP +The IRAF \fBfile i/o\fR system deals with two types of files. +\fBText files\fR contain only character data, are read and written in +units of records (lines of text), and are maintained in a such a form +that they can be edited with a host editor. +Writing may occur only at the end of file. +Reading is normally sequential, but seeking to the beginning of a line +prior to a read is permitted. The user terminal is interfaced +as a text file, opened by the IRAF main at process startup. Terminal i/o +is generally line oriented, but character at a time input is used by some +programs, and the system expects to be able to send control codes to the +terminal. Text files are accessed synchronously. Character data is always +ASCII within IRAF, with the kernel routines mapping to and from the host +character set (e.g. EBCDIC). +.PP +The second file type is the \fBbinary file\fR. A binary file is an +extendible array of machine bytes. There are two types of binary files, +\fBblocked\fR (random access) binary files and \fBstreaming\fR (sequential) +binary files. Transfers to and from blocked binary files are +always aligned on device block boundaries and are asynchronous. +The size of a transfer may be any integral multiple of the device block +size, up to a device dependent maximum transfer size. +The IRAF file i/o system (FIO) assumes that a file can be extended by +overwriting the end of file, and that a partial record can be written at +the end of file without the host filling the record to the size of a +device block. The device block size is assumed to be device dependent. +Devices with different block sizes may coexist on the same system. +.PP +Streaming binary files are for devices like magtapes and the interprocess +communication (IPC) facilities. Seeks are not permitted on streaming files, +and there are no restrictions on block size and alignment of transfers, +other than an upper limit on the transfer size. The ideal host system will +initiate an asynchronous transfer from either type of binary file directly +from the mass storage device into the buffer pointed to by the kernel +(or vice versa). +.PP +The model does not assume that the host system provides device independent +file i/o. A different set of kernel routines are provided for each device +interfaced to FIO. On a system which does provide device independent i/o +the kernel routines may be coded as calls (or multiple entry points) to a +single set of interface subroutines. Standard file devices include disk +resident text and binary files, the IPC facilities, magtapes, line printers, +and (currently) the image display devices and the batch plotters. +The special devices are normally interfaced to FIO as binary files. +The IPC files are streaming binary files. +.PP +Although it is highly desirable that the host provide a hierarchical files +system, it is not required. IRAF tends to generate and use lots of small files. +FIO maps virtual filenames into machine dependent filenames, and will pass +only filenames acceptable to the host system to the kernel routines. +It should be possible to read filenames from a host directory, +determine if a file exists and is accessible, +delete a file, and rename a file. The model assumes that there are two types +of filenames: files within the current directory (directory name omitted), +and files within a particular directory, and that both types of files can be +simultaneously accessed. The directory name is assumed to be a string which +can be prepended to the filename to produce a pathname. Multiple versions +of a file are neither assumed nor supported. +.PP +\fBMagnetic tape\fR devices are interfaced as streaming binary files. +A magnetic tape is either empty or consists of one or more files, +each delimited by an end of file (EOF) mark, with an end of tape (EOT) +mark following the last file on the tape. +The kernel routine opens the device positioned to +the first record of a specific file or EOT. Tape density may be manually +set on the device, or may be set at allocate time or at open time (the IRAF +software will work regardless of which method is used). A separate file +open is required to access each file on the tape, i.e., FIO will not try +to read or write beyond a tape mark. +.PP +Record and file skipping primitives are desirable for tape positioning in +the kernel open procedure, but are not assumed by the model. Tape records +may be variable in length. No attempt will be made to position the tape +beyond EOT. FIO knows nothing about labeled tapes or multivolume tapes; +if it is necessary to deal with such tapes, the details should be handled +either by the host system or by the kernel routines. All IRAF programs +which read and write magtape files can also be used to read and write disk +files. +.PP +The virtual machine model assumes that a parent process can spawn one or +more child \fBsubprocesses\fR, to execute concurrently with the parent, +with bidirectional streaming binary communications channels connected to +the parent. The IPC facilities are used only to communicate with child +processes; the model does not assume that any one process can talk to any +other process. The model assumes that an IPC channel can only be opened +when a child process is spawned; the two functions are bound into the +same kernel primitive. A child process is assumed to inherit the same +current working directory as the parent. The child is not assumed to +inherit environment or logical name tables, open files, or the parent's +address space. +.PP +\fBException handling\fR is very machine dependent and is difficult to model. +Fortunately the IRAF system will probably still be usable even if the host does +not entirely fit the model, since exceptions are not the norm. +A parent process is assumed to be able to interrupt a child process. For error +recovery and process shutdown to occur properly control should transfer +to an interrupt handler in the child process when the interrupt signal is sent. +.PP +All exceptions occurring during execution of a process should be caught by +the kernel and mapped into the exception classes assumed by the kernel. +The model assumes that all exceptions can be caught, that control can be +transferred to an exception handler, and that execution can resume following +processing of the exception. The host and the kernel should let the high +level software process all exceptions and handle error recovery. +.PP +In summary, IRAF can be used to do science on a limited, batch oriented host +machine, at the expense of a limited user interface and considerable hacking +of the released system. The ideal host system will provide a hierarchical +files system, large asynchronous file transfers directly into process memory, +multiprocessing, efficient binary interprocess communication facilities, +dynamic memory management facilities, and high level exception handling. + +.NH +A Reference Manual for the IRAF Kernel +.PP +The kernel is a set of SPP or Fortran callable subroutines. The syntax +and semantics of these routines, i.e., the external specifications of the +interface, are the same for all machines. The code beneath the interface +will in general be quite different for different operating systems. +Any language may be used to implement the kernel routines, provided the +routines are Fortran callable. Typed functions are avoided in the kernel +where possible to avoid the problems of passing a function value between +routines written in different languages. +.PP +The method chosen to implement a kernel routine for a given host should be +dictated by the characteristics of the host and by the external specifications +of the routine, not by the method chosen to implement the same kernel +routine for some other host. Nonetheless it is often possible to reuse +code from an existing interface when coding an interface for a new host. +This occurs most often in the numerical procedures, e.g., the bit and byte +primitives. Furthermore, some routines are placed in the kernel only +because they are potentially machine dependent; these routines need only be +examined to see if they need to be modified for the new host. +.PP +The kernel routines are found in the \fBOS package\fR, the interface to the +host Operating System (OS). The OS package is maintained in the logical +directory \fBsys$os\fR, and the kernel routines are archived in the +system library \fBlib$libos.a\fR. + +.NH 2 +Conventions +.PP +At the kernel level, data is accessed in units of \fBmachine bytes\fR. +The size of a machine byte in bits and the number of bytes per SPP data +type are both parameterized and are assumed to be machine dependent. +It is fundamentally assumed throughout IRAF that an integral number of bytes +will fit in each of the language datatypes. Conversion between byte units +and SPP units is handled by the high level code; the kernel routines deal +with data in byte units. +.PP +All offsets in IRAF are \fBone-indexed\fR, including in the kernel routines. +Thus, the first byte in a file is at offset 1, and if the device block size +is 512 bytes, the second device block is at offset 513. The first bit in a +word is bit number 1. Many operating systems employ zero-indexing instead +of one-indexing, and the implementor must be especially careful to avoid off +by one errors on such systems. +.PP +All \fBcharacter strings\fR are packed in the high level code before +transmission to the kernel, and strings returned by the kernel are unpacked +by the high level code into SPP strings. The packed string is passed in an +array of SPP type \fBchar\fR. +The format of a packed string is a sequence of zero or more characters +packed one character per byte and delimited by end-of-string (EOS). +SPP strings are ASCII +while packed strings use the host character set. The EOS delimiter occupies +one character unit of storage but is not counted in the length of the string. +Thus if a kernel routine returns a packed string of at most \fImaxch\fR +characters, up to \fImaxch\fR characters are returned followed by an EOS. +.PP +Kernel procedures should call only other kernel procedures or the host +system. Calls to program interface routines are forbidden to avoid problems +with \fBreentrancy\fR and backwards library references (and because code which +references upwards is usually poorly structured). A program crash should +never occur as a result of a call to a kernel procedure. Illegal operations +should result in return of an error status to the routine which called the +kernel procedure. The SPP error handling facilities must not be used in +kernel procedures, even if a kernel procedure can be coded in SPP. +This is because the high level code does not error check kernel procedures, +and because printing an error message involves calls to FIO and possible +reentrancy. +.PP +Any kernel procedure which can fail to perform its function returns an +integer status code as its final argument. Other integer codes are used +to parameterize input arguments, e.g., the file access modes. Codes which +are used only in a particular procedure are documented in the specifications +for that procedure. The codes used extensively in the kernel are shown in +the table below. The magic integer values given in the table must agree +with those in the SPP global include file \fBiraf.h\fR. +.sp 0.08i +.TS +center box; +cb s s +ci | ci | ci +l | c | l. +Kernel Constants +_ +name value usage += +ERR \(mi1 function was unsuccessful +EOS '\\\\0' end of string delimiter +OK 0 function successfully completed +NO 0 no (false) +YES 1 yes (true) +.TE +.sp 0.08i +.PP +The names of kernel routines never exceed six characters, and only alphanumeric +letters are used. The Fortran implicit datatyping convention (I through N for +integer identifiers) is not used in IRAF. The IRAF naming convention is +package prefix plus function plus optional type suffix letter. This convention +is little used in the kernel because there are few functions, but it does occur +in a few places, e.g. the bitwise boolean functions \fBand\fR and \fBor\fR. +The procedure naming convention and other IRAF conventions are further +discussed in the document \fIIRAF Standards and Conventions\fR. + +.NH 2 +Avoiding Library Conflicts +.PP +Only documented OS interface routines should be callable from the high level +SPP and Fortran code. If at all possible, all non-interface kernel +subprocedures and host system calls referenced in the kernel should be +named such that they are not Fortran callable. All external identifiers +used in IRAF code adhere to the Fortran standard, i.e., at most six +alphanumeric characters, the first character being a letter. Non-interface +kernel procedures are guaranteed not to cause library conflicts with the +high level software provided they are not legal Fortran identifiers. +.PP +For example, on UNIX systems, the Fortran compiler appends a hidden underscore +character to all Fortran external identifiers. No standard C library or system +procedures have names ending in an underscore, so library conflicts do not +arise. On a VMS system, the VMS system service procedures all have external +names beginning with the package prefix "sys$", hence the system service +procedures are guaranteed not to cause library conflicts with standard Fortran +identifiers. +.PP +If there is no way to avoid library conflicts by using a naming convention +at the kernel level, it is possible to modify the SPP to map identifiers in +a way which avoids the conflicts (e.g., by use of \fBdefine\fR statements +in the global include file \fBiraf.h\fR). This approach is less desirable +because it involves modification of high level code, and because it does +nothing to avoid library conflicts with Fortran subprograms which are not +preprocessed. Furthermore, it is important that the mapping of SPP +identifiers to plain vanilla Fortran identifiers be simple and predictable, +to ease interpretation of host Fortran compiler error messages, and to make +the host system debugger easy to use with SPP programs. + +.NH 2 +File I/O +.PP +The file i/o subsystem is the most critical i/o subsystem in IRAF. +No process can run without file i/o, and the high level system code and +applications programs are all built upon file i/o. Many programs are i/o +intensive, and an efficient file i/o system is vital to the functioning +of such programs. The high level of functionality and device independence +provided by the IRAF file i/o subsystem is critical to minimizing the +complexity and maximizing the flexibility of all code which uses file i/o. +In particular, the database and image i/o subsystems are heavily dependent +upon file i/o; the IRAF file i/o system was designed expressly to provide +the kinds of facilities required by these and similar applications. +.PP +Most of the complexity of the file i/o system is in the machine independent +FIO interface. FIO handles all buffer allocation and management including +management of buffer caches, record blocking and deblocking, and read ahead +and write behind. FIO makes all files and file storage devices look the same, +and allows new devices to be interfaced dynamically at run time without +modifying the system. The database facilities rely on FIO for efficient +random file access, which requires use of a buffer cache to to minimize file +faults. Image i/o relies on FIO for efficient sequential file access, +which requires asynchronous i/o and large buffers. +.PP +Kernel support for file i/o consists of a few simple file management +primitives, e.g. for file deletion and renaming, plus the "device drivers" for +the standard devices. There are two basic types of files, \fBtext files\fR +and \fBbinary files\fR. The device driver for a text device consists of +8 subroutines; the driver for a binary device consists of 6 subroutines. +A different set of driver subroutines are required for each device interfaced +to FIO. All system and device dependence is hidden within and beneath these +subroutines. Most devices are interfaced to FIO as binary files. Kernel +device drivers are closely matched in capabilities to the actual device +drivers found on many systems. +.PP +The file access modes, device parameters, and other codes used to communicate +with the kernel file i/o primitives are summarized in the table below. + +.TS +center box; +cb s s +ci | ci | ci +l | c | l. +FIO Kernel Constants +_ +name value usage += +BOF \(mi3 beginning of file +EOF \(mi2 end of file +_ +READ_ONLY 1 file access modes +READ_WRITE 2 +WRITE_ONLY 3 +APPEND 4 write at EOF +NEW_FILE 5 create a new file +_ +TEXT_FILE 11 file types +BINARY_FILE 12 +_ +FSTT_BLKSIZE 1 device block size +FSTT_FILSIZE 2 file size, bytes +FSTT_OPTBUFSIZE 3 optimum transfer size +FSTT_MAXBUFSIZE 4 maximum transfer size +.TE +.sp 0.08i + +.NH 3 +Text Files +.PP +A \fBtext file\fR is a sequence of lines of text, i.e., of characters. +Examples of text files are parameter files, list files, program source files, +and \fBterminals\fR. Although it is not strictly required, it is desirable +that text files be maintained in such a form that they can be accessed by the +host system file editor and other host utilities. The principal function +of the text file primitives is to convert text data from the host format +to the IRAF internal format, and vice versa. +.PP +The physical representation of a text file is hidden beneath the kernel +interface and is not known to an IRAF program. +The logical (IRAF) and physical (host system) representations of a text +file will in general be quite different. On some systems +it may be possible to represent a single logical text file in any of several +different physical representations, and the kernel primitives will have to +be able to recognize and deal with all such representations. On other systems +there may be only a single physical format for text files, or there may be no +distinction between text files and binary files. +.PP +The \fBlogical representation\fR of a text file is a sequence of lines of text. +Each line of text consists of zero or more ASCII characters terminated by +the \fBnewline\fR character. The newline character defaults to ASCII LF +(linefeed), but some other character may be substituted if desired. +IRAF assumes that any ASCII character can be stored in a text file; +in particular, case is significant, and control characters may be embedded +in the text. There is no fixed limit on the number of characters per line. +It may not be possible to edit a file containing arbitrarily long lines +or embedded control characters with some host system editors, but such files +are rare. +.PP +Character data is represented within IRAF with the SPP datatype \fBchar\fR. +On many systems, char is implemented as the (nonstandard) Fortran datatype +INTEGER*2. The read and write primitives +for a text file must convert SPP arrays of ASCII char to and from the +internal host representation. This conversion usually involves a packing +or unpacking operation, and may also involve conversion between ASCII and +some other character set, e.g., EBCDIC. Regardless of the precision of the +datatype used to implement char on a given host system, characters are +limited to ASCII values, i.e., 0 to 127 decimal (negative valued characters +are permitted only in SPP variables and arrays). +.PP +The kernel primitives used to access ordinary disk resident text files, +i.e., the "device driver" primitives for an ordinary text file, are shown below. +The calling sequences for other text file devices are identical if the +two character device code for the new device is substituted for the "tx" +suffix shown. A device driver is installed in FIO by passing the entry +points of the subroutines to FIO with \fBfopntx\fR or \fBfdevtx\fR; +the entry point addresses of the 8 subroutines are saved in the FIO device +table. +.PP +The \fBzopntx\fR primitive opens a text file or creates a new one, +returning the channel number (an integer magic number) or ERR as +its status value. +All subsequent references to the file are by this channel number. +The file access modes are listed in the table in \(sc4.3. +Output text is assumed to be buffered; \fBzflstx\fR is called by FIO to flush +any buffered output to the device when the file is closed, or when file output +is flushed by the applications program. +.sp 0.08i +.TS +center; +cb s +n l. +Text File Primitives +.sp +zopntx \&(osfn, mode, chan) open or create a textfile +zclstx \&(chan, status) close a textfile +zgettx \&(chan, text, maxch, status) get next record +zputtx \&(chan, text, nchars, status) put next record +zflstx \&(chan, status) flush output +znottx \&(chan, loffset) note file position +zsektx \&(chan, loffset, status) seek to a line +zstttx \&(chan, param, lvalue) get file status +.TE +.sp 0.08i +.PP +If the physical file is record oriented, there will normally be one newline +delimited line of text per record. A sequence of characters output with +\fBzputtx\fR is however not necessarily terminated with a newline. +The \fBzputtx\fR primitive is called to write the FIO output buffer when +(1) newline is seen, (2) the buffer fills, (3) the output is flushed, +or (4) the file is closed. Thus if a very long line is written, several +calls to \fBzputtx\fR may be required to output the full line. Conversely, +if the input record contains more than \fImaxch\fR characters, \fBzgettx\fR +should return the remainder of the record in the next call. In no case should +more than maxch characters be returned, as the output buffer would be overrun. +.PP +A \fBzgettx\fR call with \fBmaxch=1\fR has a special meaning when the input +device is a terminal. This call will switch the terminal from line mode to +\fBcharacter mode\fR, causing \fBzgettx\fR to return immediately each time a key +is typed on the terminal. This highly interactive mode is useful for programs +like screen editors, and is discussed further in \(sc4.3.4.1. Character mode +applies only to terminal input; if individual characters are to be output, +the output must be flushed after each character is written. +.PP +Text files are virtually always accessed sequentially. Writing is permitted +only at end of file (EOF). A file opened for reading is initially positioned +to the beginning of file (BOF). Seeking to the beginning of any line in the +file is permitted prior to a read. The seek offset must be BOF, EOF, or an +offset returned by a prior call to the \fBznottx\fR primitive, which returns +the file offset of the last text line read or of the next text line to be +written. Seeks on text files are restricted to lines; seeks to individual +characters are not permitted. The long integer file offset returned by +\fBznottx\fR is a magic number, i.e., the value is assumed to be machine +dependent. +.NH 3 +Binary Files +.PP +A \fBbinary file\fR is an extendible array of bytes accessed in segments the +size of a device block. The principal difference between a text file and +a binary file is that character data is converted in some machine dependent +fashion when a text file is accessed, whereas data is copied to and from +a binary file without change. A second difference is that only binary files +are randomly accessible for both reading and writing at any offset. +Binary files are used to implement interprocess communication (IPC) files, +database files (datafiles), picture storage files (imagefiles), and so on. +Special devices such as magtape, the line printer, image display devices, +and process memory are interfaced to FIO as binary files. +.PP +The fundamental unit of storage in a binary file is the \fBmachine byte\fR. +The number of bits per byte is presumed to be machine dependent, although +IRAF has thus far been used only on machines with 8 bit bytes. IRAF assumes +only that there are an integral number of bytes in each SPP or Fortran datatype. +The SPP datatype \fBchar\fR should not be confused with the machine byte. +The char is the fundamental unit of storage in SPP programs; the number +of machine bytes per SPP char is greater than or equal to one and is given +by the machine dependent constant SZB_CHAR, defined in \fBiraf.h\fR. +Though the distinction between chars and machine bytes is important in the +high level system code, it is of no concern in the kernel since the kernel +routines access binary files only in units of machine bytes. +.PP +Binary files are further differentiated into \fBblocked\fR (random access) +binary files and \fBstreaming\fR (sequential) binary files. The most +common blocked binary file is the binary random access disk file. +IPC files and magtape files are typical streaming binary files. +The \fBdevice block size\fR is used to differentiate between blocked and +streaming binary files. A device with a block size greater than or equal +to one byte is understood to be blocked, whereas a device with a block size +of zero is understood to be a streaming file. Transfers to and from blocked +devices are always aligned on device block boundaries. There are no alignment +restrictions for streaming files. +.sp 0.08i +.TS +center; +cb s +n l. +Binary File Primitives +.sp +zopnbf \&(osfn, mode, chan) open or create a binary file +zclsbf \&(chan, status) close a binary file +zardbf \&(chan, buf, maxbytes, loffset) initiate a read at loffset +zawrbf \&(chan, buf, nbytes, loffset) initiate a write at loffset +zawtbf \&(chan, status) wait for transfer to complete +zsttbf \&(chan, param, lvalue) get file status +.TE +.sp 0.08i +.PP +The kernel primitives used to access ordinary disk resident binary files, +i.e., the "device driver" primitives for a binary file, are shown above. +The calling sequences for other binary file devices are identical if the +two character device code for the new device is substituted for the "bf" +suffix shown. The device driver for a binary file is particularly simple +since all buffering is performed at a high level. A binary file is opened +or created with \fBzopnbf\fR, which returns the channel number or ERR as +its final argument. All subsequent references to the file are by channel +number. +.PP +The kernel primitives for a binary file closely approximate the functionality +of the typical host system device driver. +Ideally an asynchronous kernel read or write +to a binary file will translate into a DMA transfer directly from the data +buffer to the device, or vice versa. FIO guarantees that only a single transfer +will be pending on a channel at a time, i.e., that a new i/o request will not be +issued until any previous transfer is completed. There is no \fBseek\fR +primitive for binary files since the absolute file offset is specified in +a read or write request (the file offset argument should be ignored for a +streaming file). There is no \fBnote\fR primitive since there is no concept +of the current file position for a binary file at the kernel level. +.PP +The \fBzardbf\fR and \fBzawrbf\fR (asynchronous read and write) primitives +should initiate a transfer and return immediately. No status value is +returned by these primitives: rather, the number of bytes read or written +or ERR is returned in the next call to \fBzawtbf\fR (asynchronous wait). +A byte count of zero on a read indicates end of file. It is not an error +if fewer than \fBmaxbytes\fR bytes can be read; \fBzardbf\fR should return +immediately with whatever data it was able to read, rather than try to +read exactly maxbytes bytes. In no case should more than maxbytes bytes +be returned, as this would overflow the caller's buffer. If the size of the +input block or record is greater than maxbytes bytes when reading from a +streaming file, data is assumed to be lost. An attempt to read or write +before BOF or after EOF is illegal and will be caught by FIO. +.PP +The status value ERR should be returned for all illegal requests or +i/o errors. FIO will always call \fBzawtbf\fR after a transfer for +synchronization and to check the status value. Repeated calls to \fBzawtbf\fR +after a single i/o transfer should continue to return the same status. +Errors should not "stick", i.e., an error status should be cleared when +the next transfer is initiated. +.PP +It is fundamentally assumed that it is possible to extend a file by +overwriting EOF in a call to \fBzawrbf\fR. It is further assumed that the +last block in a file need not be full. For example, suppose the device +block size is 512 bytes, the FIO buffer size is 512 bytes, and we are +writing to a file 1024 bytes long. We write 18 bytes at file offset 1025, +the fourth block in the file, and then close the file. +When the file is subsequently reopened, FIO will call \fBzsttbf\fR to get +the file size, which should be 1042 bytes. If the program calling FIO +then attempts to write 50 bytes at EOF, FIO will call \fBzardbf\fR to initiate a +read of 512 bytes at file offset 1025, and a subsequent call to \fBzawtbf\fR +will return a byte count of 18. FIO will copy the 50 bytes into the buffer +starting at byte offset 19 and eventually write the buffer to the file +at file offset 1025, overwriting EOF and extending the file. +.PP +.NH 3 +Specifying Device Parameters +.PP +Each device interfaced to FIO has a unique status primitive callable while +the file is open to obtain values for the device parameters. +These parameters reflect the characteristics of the \fIdevice\fR +or \fIfilesystem\fR on which the file is stored. +The status primitives for disk resident text and binary +files are \fBzstttx\fR and \fBzsttbf\fR. These primitives are the only file +status primitives available for a file while it is open; \fBzfinfo\fR is not +called to get information on open files. +.PP +The device block size and file size parameters are currently not used for +text files, although they are read when the file is opened. +The file size parameter is not needed for text files because \fBzsektx\fR +is used to seek to EOF on a text file. All four parameters are required for +binary files. +.sp +.in 1.0i +.ti -0.5i +FSTT_BLKSIZE +.sp 0.04i +The device block size in bytes (binary files only). A block size of zero +indicates a streaming device; no alignment checking will be performed, +and only sequential i/o will be permitted. +If the block size is greater than or equal to one, the device is understood +to be a random access binary file with the indicated device block size. +File reads and writes will be aligned on device block boundaries, +although if the block size is given as 1 byte (e.g., if process memory is +accessed as a file) there is effectively no restriction on block alignment. +.sp +.ti -0.5i +FSTT_FILSIZE +.sp 0.04i +The file size in bytes; zero should be returned for a new file. +Not used for streaming files. FIO will ask for this once, when the file +is opened, if the file is a regular disk resident binary file. +Thereafter FIO keeps track of the file size itself. If necessary the +kernel can get the file size from the host system before opening the file. +.sp +.ti -0.5i +FSTT_OPTBUFSIZE +.sp 0.04i +The optimum transfer or buffer size for "normal" file access. This parameter +defines the default FIO buffer size for both read and write access. +The optimum transfer size typically depends on the characteristics of +the i/o system of the host, and may also depend on the characteristics +of the device or of the file system on which the file is found. +For example, the optimum transfer size of a file system configured for +the storage of images (large files) may well be larger than that for a +file system configured for general use (predominantly small files). +.sp +.ti -0.5i +FSTT_MAXBUFSIZE +.sp 0.04i +The maximum permissible transfer size in a single read or write request. +This parameter determines the maximum FIO buffer size, and hence the +maximum size of a FIO read or write request. +.in -1.0i +.sp +.PP +The optimum buffer size for magtape devices is usually different (larger) +for reading than for writing; the default magtape buffer sizes are set by +system tuning parameters in \fBconfig.h\fR (\(sc6). FIO automatically +adjusts the internal FIO buffer size for a file to be an integral multiple +of the device block size. The FIO buffer size may be further controlled +at a high level by advising FIO that i/o to a file is to be highly random +or highly sequential. With all this going on in the high level code, +it is inadvisable to try to tune the system by adjusting the device +parameters. The file status parameters should reflect the physical +characteristics of the device or files system on which the file is resident. +.PP +For example, consider a random access binary disk file on a UNIX system. +The device block size will typically be 512 bytes and is generally wired +into the status primitive as a constant. The file size may be obtained +at any time from the inode for the file. The optimum buffer size is +512 bytes on V7 UNIX, 1024 bytes on 4.1BSD, and dependent on how a filesystem +is configured on 4.2BSD. There is no maximum transfer size for a disk +file, so the maximum integer value is returned. If the file happens to +be a pipe, the block size would be given as 0, the file size is ignored, +the optimum transfer size is arbitrary, e.g. 2048 bytes, and the maximum +transfer size is typically 4096 bytes. +.NH 3 +Standard File Devices +.PP +The kernel routines for the ordinary text and binary disk files have +already been presented. An arbitrary number of other devices may be +simultaneously interfaced to FIO. The standard devices are disk, memory, +terminal, line printer, IPC, magtape, and the pseudofiles (STDIN, STDOUT, +etc.). The memory and pseudofile interfaces are machine independent and +will not be discussed here. The IRAF development system currently also +supports file interfaces for image display devices and plotters, but the +graphics device interfaces are being redesigned to use the ISO standard +Graphical Kernel System (GKS) graphics device interface, so we will not +discuss those devices here. +.PP +Each device is interfaced with a distinct set of kernel interface routines +to give the implementor maximum scope for tailoring the interface to a +device. If the host system provides device independent file i/o at a low level, +it may be possible to use the same kernel routines for more than one device. +For example, the text file driver might be used for both disk resident text +files and terminals, and the IPC, magtape, and line printer devices might +resolve into calls to the kernel routines for a disk resident binary file. +This approach offers maximum flexibility for minimum effort and should be +followed if the host system permits. On the other extreme, a host might +not have something fundamental like IPC channels, and it might be +necessary to build the driver from the ground up using non-file resources +such as shared memory. +.NH 4 +The User Terminal +.PP +Terminal devices are interfaced to FIO as text files. The device code +is "ty". The driver subroutines are shown below. The legal access +modes for a terminal are READ_ONLY, READ_WRITE, WRITE_ONLY, and APPEND. +Seeking to offsets other than BOF or EOF is illegal; seeks to BOF and +EOF should be ignored. +.sp 0.08i +.TS +center; +cb s +n l. +Terminal Driver +.sp +zopnty \&(osfn, mode, chan) open a terminal file +zclsty \&(chan, status) close a terminal +zgetty \&(chan, text, maxch, status) get next record +zputty \&(chan, text, nchars, status) put next record +zflsty \&(chan, status) flush output +znotty \&(chan, loffset) not used +zsekty \&(chan, loffset, status) not used +zsttty \&(chan, param, lvalue) get file status +.TE +.sp 0.08i +.PP +When an IRAF process is run from the CL it communicates with the CL via +IPC files; when not run from the CL, an IRAF process assumes it is talking +to a terminal. The terminal driver is therefore linked into every IRAF main. +The main assumes that the terminal is already open when an IRAF process +starts up; the \fBzopnty\fR and \fBzclsty\fR routines are used only when +a terminal is directly accessed by a program. +.PP +If possible the terminal driver should be set up so that input can come +from either a terminal or an ordinary text file, allowing IRAF processes +to be run in batch mode taking input from a file. On a batch oriented +system the "terminal" driver would be the same as the text file driver, +and input would always come from a file. +.PP +Terminal input is normally line oriented. The host terminal driver +accumulates each input line, handling character, word, and line deletions and +other editing functions, echoing all normal characters, checking for control +characters (e.g. interrupt), and returning a line of text to \fBzgetty\fR +when carriage return is hit. The line returned by \fBzgetty\fR to the +calling program should always be terminated by a \fBnewline\fR. +.PP +If \fBzgetty\fR is called with \fBmaxch=1\fR the terminal is put into raw +character mode. In this mode \fBzgetty\fR returns each character as it +is typed, control characters have no special significance (as far as possible), +and characters are not automatically echoed to the terminal. A subsequent +call with maxch greater than one causes a mode switch back to line input +mode, followed by accumulation of the next input line. +.PP +The IRAF system includes device independent software for terminal control +and vector graphics, and expects to be able to send device dependent control +sequences to the terminal. Any program which does anything out of the ordinary +with a terminal, e.g., clearing the screen or underlining characters, uses +the TTY interface to generate the device dependent control sequences necessary +to control the terminal. Ordinary output to the terminal, however, is not +processed with the TTY interface. +.PP +The control characters commonly present in ordinary text are \fBnewline\fR, +\fBcarriage return\fR, and \fBtab\fR. The \fBnewline\fR character delimits +lines of text and should result in a carriage return followed by a line feed. +\fBZputtx\fR may be called repeatedly to build up a line of text; the output +line should not be broken until newline is sent. The carriage return character +should cause a carriage return without a line feed. If the host system terminal +driver can conditionally expand tabs, tab characters present in the text +should be passed on to the host driver. The terminal should be allowed +to expand tabs if possible as it is much faster, especially when working +from a modem. On many systems it will be necessary to map newlines upon +output, but all other control characters should be left alone. +.NH 4 +The Line Printer Device +.PP +Line printers are interfaced at the kernel level as binary files. +At the FIO level a line printer may be opened as either a text file or +a binary file. If opened as a text file at the FIO level, textual +output is processed under control of the device independent TTY interface, +generating the control sequences necessary to control the device, +then the output is packed and written to the device as a binary byte +stream. If the printer is opened as a binary file at the high level, +binary data is passed from the applications program through FIO and on +to the kernel without modification. +.sp 0.08i +.TS +center; +cb s +n l. +Line Printer Driver +.sp +zopnlp \&(osfn, mode, chan) open printer or spoolfile +zclslp \&(chan, status) close printer +zardlp \&(chan, buf, maxbytes, notused) initiate a read +zawrlp \&(chan, buf, nbytes, notused) initiate a write +zawtlp \&(chan, status) wait for transfer to complete +zsttlp \&(chan, param, lvalue) get file status +.TE +.sp 0.08i +.PP +The line printer is a streaming device. Currently only APPEND mode +is used, although there is nothing in the FIO interface to prevent +reading from a line printer device. The filename argument is the +\fBlogical name\fR of the printer device, as defined by the CL environment +variable \fBprinter\fR and as found in the \fBdev$termcap\fR file. +These logical device names are quite system dependent, and in general +it will be necessary to add new device entries to the termcap file, +and change the name of the default printer device by modifying the +\fBset printer\fR declaration in \fBlib$clpackage.cl\fR. +.PP +On some systems or for some devices it may be desirable to spool printer +output in an ordinary binary file opened by \fBzopnlp\fR, disposing of +the file to the host system when \fBzclslp\fR is called, or at some +later time. This is desirable when the line printer device is so slow +that asynchronous printing is desired, or when the printer device is +located on some other machine and the spoolfile must be pushed through +a network before it can be output to the device. +.PP +In general it should not be necessary to modify printer data upon output. +The high level code processes form feeds, expands tabs, generates control +sequences to underline characters, breaks long lines, maps newline into +the device end of line sequence, pads with nulls (or any other character) +to generate delays, and so on, as directed by the \fBdev$termcap\fR file. +If the host system driver insists on processing printer output itself, +it may be necessary to modify the termcap entry for the printer to generate +whatever control sequences the host system requires (the newline sequence +is likely to be a problem). The termcap entry for a printer is potentially +machine dependent since raw output to a line printer may not be feasible on +some systems, and it may be easier to edit the termcap file than to filter +the output stream in the driver. +.PP +Part of the reason for implementing the printer interface as a binary file +was to provide a convenient and efficient means of passing bitmaps to printer +devices. If a bitmap is to be written to a printer device, ideally the +device will be data driven and it will be possible to pass data directly +to the device without translation. If this is not the case, the driver +must make the device look like it is data driven by scanning the data +stream for a control sequence indicating a change to bitmap mode, +then poking the host driver to change to bitmap mode. Since the device +is data driven at the kernel level it will still be possible to spool +the output in a file and process it on a remote network node. +.NH 4 +Interprocess Communication +.PP +Interprocess communication (IPC) channels are necessitated by the multiprocess +nature of IRAF. When a subprocess is spawned by the CL (or by any IRAF +process) it is connected to its parent by two IPC channels, one for reading +and one for writing. An IPC channel is a record oriented streaming binary +file. +.sp 0.08i +.TS +center; +cb s +n l. +IPC Driver +.sp +zopnpr \&(osfn, mode, chan) not used +zclspr \&(chan, status) not used +zardpr \&(chan, buf, maxbytes, notused) initiate a read +zawrpr \&(chan, buf, nbytes, notused) initiate a write +zawtpr \&(chan, status) wait for transfer to complete +zsttpr \&(chan, param, lvalue) get file status +.TE +.sp 0.08i +.PP +The IPC channels are set up when a subprocess is spawned, and a process may +use IPC facilities only to talk to its parent and its children (the process +structure is a tree, not a graph). Since the opening of an IPC channel is +bound to the spawning of a subprocess, the open and close primitives are +not used for IPC files. The \fBprconnect\fR procedure (not a kernel primitive), +called by the parent to spawn a subprocess, sets up the IPC channels and +installs the IPC driver in FIO, +returning two binary file descriptors to the calling program. +The \fBprconnect\fR procedure is very much like a file \fBopen\fR, except that +the "file" it opens is active. The IRAF main in the child process senses +that it has been spawned by an IRAF process, and installs the same IPC driver +in its FIO, connecting the IPC channels to the streams CLIN and CLOUT. +.PP +Since the IPC channels are read and written by concurrent processes, +some care is necessary to ensure synchronization and to avoid deadlocks. +Fortunately most of the necessary logic is built into the high level protocols +of the CL interface, and an understanding of these protocols is not necessary +to implement the IPC driver. It is however essential that the low level +protocols of an IPC channel be implemented properly or deadlock may occur. +.PP +An IPC channel is \fBrecord oriented\fR. This means that if \fBzawrpr\fR +is called by process A to write N bytes, and \fBzardpr\fR is called by +process B to read from the same channel, N bytes will be read by process B. +If the IPC facilities provided by the host are sufficiently sophisticated, +records may be \fBqueued\fR in an IPC channel. The writing process should +block when the IPC channel fills and no more records can be queued. The +reading process should block when it attempts to read from an empty channel. +.PP +For example, suppose process A writes an N byte record and then an M byte +record. If \fBzardpr\fR is called by process B to read from the channel, +it should return to its caller the first record of length N bytes. +A second call will be required to read the next record of length M bytes. +On some systems, e.g. UNIX, the IPC facilities are not record oriented and the +first read might return either N bytes or N+M bytes, depending on unpredictable +system timing details. Hence the IPC driver for a UNIX system must impose +a record structure upon the UNIX "pipe" used as the IPC channel. +.PP +On other systems the IPC facilities may be limited to the transfer of single +records, i.e., process B will have to read a record +before process A can transmit the next record. This is the lowest common +denominator, and hence the protocol chosen for the IPC driver. Despite the +use of the lowest common denominator for the low level protocol, a high +bandwidth can be achieved for IPC channels if the maximum transfer size +is large and records are queued. The high level protocol ensures that only +one process will be writing or reading at a time, thus preventing deadlock. +The high level protocol also uses special data records as semaphores to achieve +synchronization and permit record queuing. +.PP +Most modern operating systems provide some sort of interprocess communications +facilities which the IPC driver can use. UNIX calls it a pipe or a socket, +VAX/VMS calls it a mailbox, and DG/AOS calls it an IPC port. If the host +system has no such facility, or if the facility provided by the host is +inefficient, an IPC driver can often be built using shared memory (use a +circular buffer to implement the queue). As a last resort, a real driver +can be coded and installed in the host system. On \fBmulti-processor\fR +systems the IPC facilities should allow the parent and child processes to +reside on different processors. +.NH 4 +Imagefile Access +.PP +Imagefiles, i.e., bulk data files, are a special kind of binary file. +The ordinary disk binary file driver may be used to access imagefiles, +but imagefiles have certain properties which can be exploited on some +systems for increased i/o efficiency. The image i/o software (IMIO) therefore +uses a special kernel driver to access imagefiles. Since this driver is +a special case of the ordinary binary file driver, a transportable version +of the driver which simply calls the ordinary binary file driver is included +in the standard distribution. The transportable driver may easily be replaced +by a machine dependent version to optimize image i/o for the host system. +.PP +Imagefiles differ from ordinary binary files in that the size of the image +is known when the \fBpixel storage file\fR is created. Furthermore, images +do not dynamically change in size at run time. On many systems it is possible +to preallocate the pixel storage file before writing any data into it, +rather than creating the file by writing at EOF. +.PP +Preallocation of a file makes it feasible for the host system to allocate +\fBcontiguous storage\fR for the file. Use of a preallocated, fixed size +file also makes it possible on some systems to map the file into +\fBvirtual memory\fR. If image access is expected to be sequential, or if +the host system does not support virtual memory, it is often possible to +\fBdirectly access\fR the file via the host system device driver, bypassing +the host files system software and significantly reducing the overhead of +file access (e.g., eliminating any intermediate buffering by the host system). +.sp 0.08i +.TS +center; +cb s +n l. +Static File Driver +.sp +zopnsf \&(osfn, mode, chan) open static file +zclssf \&(chan, status) close static file +zardsf \&(chan, buf, maxbytes, loffset) initiate a read +zawrsf \&(chan, buf, nbytes, loffset) initiate a write +zawtsf \&(chan, status) wait for transfer to complete +zsttsf \&(chan, param, lvalue) get file status +.sp +zfaloc \&(osfn, nbytes, status) preallocate a binary file +.TE +.sp 0.08i +.PP +The use of a file i/o interface to implement virtual memory access to files +is desirable to minimize the machine dependence of applications which use +virtual memory. The functional behavior of the static file driver is the +same whether it maps file segments into virtual memory or copies file +segments into physical memory. If IRAF is to be used on a system which does +not provide virtual memory facilities, the image processing software +will work without modification, provided the physical memory requirements +of the software are reasonable. +.PP +FIO divides a file up into segments of equal size, where the size of a segment +is equivalent to the size of a file buffer and is an integral multiple of the +virtual memory page size. IMIO ensures that the pixel data in the pixel +storage file begins on a block boundary, and is an integral number of pages +in length. Furthermore, when FIO allocates a file buffer, it ensures that +the buffer is aligned on a virtual memory page boundary. The virtual memory +page size is parameterized in \fBconfig.h\fR, and is set to 1 on a nonvirtual +machine. +.PP +Provided that the buffer and the file data are both properly aligned, +\fBzardsf\fR may be used to map a file segment into memory. The file buffer +pages are first deleted and then remapped onto the new file segment. +If the buffer is written into, \fBzawrsf\fR will eventually be called to +update the segment, i.e., flush modified pages to the image file (the pages +should not be unmapped). If desired a single FIO buffer may be allocated +the size of the entire image and all reads and writes will reference this +single buffer with minimum overhead. Alternatively the image may be mapped +in segments; reusing the same buffers avoids flushing the system page cache +when sequentially accessing a large image. +.PP +When an image section is read or written by IMIO, the interface returns +a pointer to a buffer containing the pixels. If all of the necessary +conditions are met (e.g., no subsampling, no datatype conversion, etc.), +IMIO will return a pointer directly into the file buffer, otherwise IMIO +extracts the pixels from the file buffer into a separate buffer. If the file +buffer is mapped onto the imagefile, IMIO thus returns a pointer directly into +the imagefile without performing any i/o (until the data is referenced). +Thus it is possible to exploit virtual memory for image access without +restricting the flexibility of programs which operate upon images; general image +sections may be referenced, datatypes need not agree, etc., yet i/o will still +be optimal for simple operations. +.PP +For example, suppose an entire large image is to be mapped into virtual +memory. FIO does not allocate any buffers until the first i/o on a file +occurs. IMIO will be called by the applications program to read a "subraster" +the size of the entire image. If the image can be directly accessed, +IMIO will set the FIO buffer size to the size of the image, then issue a +\fBseek\fR and a \fBread\fR to read the pixels. +FIO will allocate the buffer, aligned on a page boundary, then call +\fBzardsf\fR which maps the buffer onto the pixel storage file. +.PP +If all the necessary conditions are met, IMIO will return a pointer into the +FIO buffer and hence to the segment of memory mapped onto the pixel storage +file. If this is not possible, IMIO will allocate a new buffer of its own +and perform some transformation upon the pixels in the FIO buffer, writing +the transformed pixels into the IMIO buffer. The IMIO pointer will be +dereferenced in an subprogram argument list in the applications program, +and the SPP or Fortran subprogram will see what appears to be a static array. +.PP +Most virtual memory implementations are designed more for random access than +for \fBsequential access\fR. +Some systems, e.g. VAX/VMS, allow a variable number of pages (the page fault +cluster) to be read or written when a page fault occurs. +Other systems, e.g., DG/AOS, read or write a single page for each fault. +Even when the page fault cluster can be made large to minimize faulting +when sequentially accessing a large image, i/o is not optimal because paging +is not asynchronous, and because the heavy faulting tends to flush the process +and system page caches. Thus for sequential image operations conventional +double buffering with large buffers and large DMA transfers direct from +disk to memory is preferable. This level of i/o is available via QIO calls +on VAX/VMS and is feasible via \fIphysio\fR calls on UNIX, if images are +static and contiguous or nearly contiguous. +.PP +If the host system provides both virtual memory facilities and low level +asynchronous i/o, the static file driver should ideally be capable of +performing i/o by either technique. The choice of a technique may be based +upon the alignment criteria and upon the size of the transfer. +If the alignment criteria are not met or if the size of the transfer is +below a threshold, conventional i/o should be used. If the size of the +transfer is large, e.g., some sizable fraction of the working set size, +virtual i/o should be used. Virtual memory should only be used for images +which are to be accessed randomly, so the page fault cluster should be small. +.NH 4 +Magtape Devices +.PP +The magnetic tape device interface is the most complex file device interface +in the IRAF system. Operating systems vary greatly in the type of i/o +facilities provided for magtape access, making it difficult to design a +machine independent interface. Some systems provide primitive access to +the drive, permitting file and record skipping, writing of tape marks, +and so on, while others permit only sequential access in the forward direction. +Magtape access is further complicated by the storage of multiple files on +a tape, by variable size records, the occasional need to swap bytes, +and the need to specify the density. Error recovery is particularly difficult +for magtape devices because it is possible to lose track of the position +of the tape: whereas most binary devices are accessed by absolute offset, +magtapes are accessed relative to the current position. +.PP +To avoid having to deal with this level of complexity in the kernel, +the magtape device driver has been subdivided into a machine independent +part and a unique magtape device interface. The standard streaming +binary file driver subroutines are coded in SPP and are portable. An inner +set of six "zz" routines are defined especially for accessing magtape +devices. The portable driver routines constitute the MTIO interface and +are not part of the kernel. MTIO opens and initializes multiple magtape +devices, keeps track of the file position, and handles error recovery. +The kernel routines are responsible for physically positioning the tape +and for reading and writing records. +.sp 0.08i +.TS +center; +cb s +n l. +Magtape Driver +.sp +zopnmt \&(osfn, mode, chan) open a magtape file +zclsmt \&(chan, status) close magtape device +zardmt \&(chan, buf, maxbytes, notused) initiate a read +zawrmt \&(chan, buf, nbytes, notused) initiate a write +zawtmt \&(chan, status) wait for transfer to complete +zsttmt \&(chan, param, lvalue) get file status +.TE +.sp 0.08i +.PP +A magtape device must be \fBallocated\fR at the CL level before the device +can be accessed. A file on a magtape device is opened by calling the program +interface procedure \fBmtopen\fR in an applications program. +Once opened, a magtape file is accessed via the FIO interface and +hence benefits from the buffer management facilities provided by FIO. +Use of the FIO interface also provides device independence, allowing programs +which access magtape to be used to (sequentially) access any other binary +file. In particular, any IRAF program which commonly accesses magtape may +also be used to access a disk file. This permits use of the FITS reader and +writer, for example, for image transmission between stranger machines in a local +area network. +.PP +When a magtape device is allocated a device \fBlock file\fR is written into +the IRAF public directory \fBdev$\fR by the high level code. In addition to +telling other IRAF processes that the device has been allocated, the lock file +is used to keep track of the tape position while the device is closed. +When a device is closed, either normally or during error recovery, a new lock +file is written recording the current position of the drive as well as various +statistics (owner, time of last access, number of records read or written, +etc.). When a device is opened the lock file is read to determine the +current position. The \fBsystem.devstatus\fR program prints the contents +of the device lock file; if there is no lock file, the IRAF system assumes +that the device has not been allocated. +.PP +Each file on a reel must be opened individually, just as each file on a disk +must be opened individually. A pair of calls to \fBmtopen\fR and \fBclose\fR +(FIO) are required to access each file. +Drives are referred to by the logical names "mta", "mtb", and so on. +The assignment of logical drives to physical devices is system dependent. +The logical drive number, density, absolute file number on the tape, +absolute record number within the file, access mode, and FIO buffer size +(most of which can be defaulted) are specified in the file "name" argument +when the file is opened. For example, the filespec "mtb1600[3,10]" refers +to record 10 of file 3 of logical drive "mtb" at 1600 bpi. The minimum +filespec consists of just the logical drive name; everything else is optional. +.PP +The \fBmtopen\fR procedure parses the magtape filespec to determine whether +a magtape device or a disk binary file is being referenced. If a disk file +is named, \fBmtopen\fR reduces into a conventional call to \fBopen\fR. +If a magtape file is named, the filespec is parsed to determine the logical +drive, density, and the file and record numbers to which the tape is to +be opened. If this information is legal, if the drive is allocated, +and if the drive is not already open, \fBmtopen\fR reads the lock file to +determine the current position. FIO is then called to open the device. +A global common is used to pass device dependent information from \fBmtopen\fR +to \fBzopnmt\fR, since device dependent information cannot be passed through +FIO. +.PP +The magtape kernel primitives are shown below. Our intent here is only to +introduce the routines and discuss the role they fulfill in the MTIO interface. +Detailed specifications for the routines are given in the manual pages. +.sp 0.08i +.TS +center; +cb s +n l. +Magtape Kernel Primitives +.sp +zzopmt \&(drive, density, mode, oldrec, oldfile, newfile, chan) open +zzclmt \&(chan, mode, nrecords, nfiles, status) close +zzrdmt \&(chan, buf, maxbytes) aread +zzwrmt \&(chan, buf, nbytes) awrite +zzwtmt \&(chan, nrecords, nfiles, status) await +zzrwmt \&(chan, status) arewind +.TE +.sp 0.08i +.PP +The \fBzopnmt\fR procedure is called by FIO to open a magtape device. +The only legal access modes for magtape files are READ_ONLY and WRITE_ONLY. +The device parameters are retrieved from the global common prepared by +\fBmtopen\fR and passed on to the kernel primitive \fBzzopmt\fR to physically +open the drive. The kernel open primitive sets the density of the drive and +opens the drive with the desired access mode, leaving the tape positioned to +record 1 of the desired file. +.PP +The exact position of the tape at open time, i.e., both file and record +numbers (one-indexed), is passed to \fBzzopmt\fR to facilitate positioning +the tape. Many systems can skip records and tapemarks in either the forward +or reverse direction, and it is easy to position the tape on such systems +given the current position and the new position. The current record number +is needed to tell \fBzzopmt\fR when the current file is already rewound. +On some systems it is not possible to backspace to the last tapemark, +and the only way to rewind the current file or position to a previous file +is to rewind the tape and read forward. +.PP +Upon input to \fBzzopmt\fR, the \fInewfile\fR argument specifies the number +of the file to which the tape is to be positioned, or the magic number EOT. +Upon output, \fInewfile\fR contains the number of the file to which the +tape was actually positioned. The high level code assumes that the +tape does not move when the device is closed and subsequently reopened; +if this is not the case, \fBzzopmt\fR should ignore the old position arguments. +.PP +The \fBzzrdmt\fR and \fBzzwrmt\fR primitives initiate an asynchronous +read or write of a record at the current tape position. The number of bytes +read or written and the number of records and or files skipped in the operation +are returned in the next call to \fBzzstmt\fR. If a tape mark is seen when +attempting to read the next record, \fBzzwtmt\fR should return a byte count +of zero to signal EOF. It does not matter whether the tape is left positioned +before or after the tape mark, provided the record and file counts are +accurate. +.PP +A file containing zero records marks logical EOT. If physical EOT is seen +either the host system or the kernel should signal the operator to mount +the next reel, returning only after a record has been read or written on +the next reel. The high level code does not try to reread or rewrite +records. Error retry should be handled either by the kernel routines or +preferably by the host driver. The kernel should return ERR only if it +cannot read or write a record. It is not an error if less data is read +than was requested, or if more data was available in the record than was +read, resulting in loss of data. Loss of data is unlikely because FIO +generally allocates a large (16K or 32K) input buffer when reading magtapes. +.PP +If an error occurs when accessing the magtape device, e.g. a keyboard +interrupt, the high level code will mark the position of the tape as +undefined and call \fBzzclmt\fR to close the device. When the device is +subsequently reopened, \fBmtopen\fR will see that the position of the tape +is undefined and will rewind the tape before calling \fBzzopmt\fR to open +and position the drive. Since this is handled by MTIO, the kernel routines +need not be concerned with error recovery except possibly to abort a tape +motion if an interrupt occurs, to prevent runaway (hopefully this will not +be necessary on most systems). +.PP +The \fBzzclmt\fR primitive will be called to close the device upon normal +termination or during error recovery. MTIO assumes that \fBzzclmt\fR will +write an EOT mark (two tapemarks) \fIat the current tape position\fR when +a tape opened with write permission is closed. This is the only way in which +MTIO can write EOF and EOT marks on the tape. To avoid clobbering tapes, +\fBzzopmt\fR may need to open the drive read-only while positioning the tape. +Since an interrupt may occur while the tape is being positioned, \fBzzopmt\fR +should return the OS channel argument immediately after the channel has been +opened, before positioning the tape. +.PP +In summary, the magtape kernel primitives are highly machine independent +because they permit access to only a single file at a time, reading or +writing sequentially in the forward direction. No primitive tape positioning +commands are required except \fBzzrwmt\fR, and that can be implemented +with a call to \fBzzopmt\fR if necessary (the difference is that \fBzzrwmt\fR +may be asynchronous). No assumptions are made about where the tape is left +positioned if an error occurs or if a tapemark is read or written. +All writing of tapemarks is left to the kernel or to the host system. + +.NH 2 +Filename Mapping +.PP +The syntax of a filename is highly system dependent. This poses a major +obstacle to transporting a large system such as IRAF, since the standard +distribution consists of several thousand files in several dozen directories. +Many of those files are referred to by name in the high level program sources, +and use of host system dependent filenames in such a context would make the +IRAF system very difficult to transport. +.PP +To avoid this problem only \fBvirtual filenames\fR (VFNs) are used in IRAF +source files. FIO converts a VFN into a host system dependent filename (OSFN) +whenever a filename is passed to a kernel routine. Conversely, when FIO reads +a directory it converts the list of OS filenames in the host directory into +a list of virtual filenames. The kernel routines see only machine dependent +filenames packed as Fortran character constants. +.PP +While it is not necessary to study filename mapping to implement the kernel, +filename mapping is a vital part of the system interface and an understanding +of the mapping algorithm employed is necessary to adjust the machine dependent +parameters controlling the mapping. The filename mapping parameters are given +in the system configuration file \fBlib$config.h\fR. +.NH 3 +Virtual Filenames +.PP +A VFN consists of three fields, the \fBdirectory\fR, the \fBroot\fR, +and the \fBextension\fR. Directories are specified by a \fBlogical +directory\fR name followed by a \fBpathname\fR to a subdirectory. +Either the logical directory name or the pathname may be omitted. +If the logical directory field is omitted the current directory is assumed. +The extension field is optional and is used to specify the file type, +e.g., CL source, SPP source, object module, and so on. Either the logical +directory delimiter character \fB$\fR or the subdirectory delimiter +character \fB/\fR may delimit the directory field, and a period delimits +the root and extension fields. +.DS + \fIdir\fR \fB$\fR \fIpath\fR \fB/\fR \fIroot\fR \fB.\fR \fIextn\fR +.DE +.PP +The combined length of the root and extension fields is limited to 32 +characters. The legal character set is \fBA-Za-z0-9_.\fR, i.e., +the upper and lower case alphanumerics (case is significant), underscore, +and period. Other characters may be permitted on some systems, but if +present the filename is machine dependent. The first character of a filename +is not special, i.e., the first character may be a number, underscore, +or any other filename character. Purely numeric filenames are permitted. +The VFN syntax does not support VAX/VMS-like version numbers. A file naming +syntax would not be sufficient to emulate versions; extensive FIO support +would also be required on systems other than VMS. +.PP +The following are all legal virtual filenames (avoiding directory specifications +for the moment). +.DS + 20 + 02Jan83 + Makefile + 10.20.11 + M92_data.Mar84 + extract_spectrum.x + _allocate +.DE +.NH 4 +Logical Directories and Pathnames +.PP +The use of logical directories and pathnames is perhaps best explained by +an example. Consider the VFN \fBplot$graph.x\fR, specifying the file +\fBgraph.x\fR in the logical directory \fBplot\fR. +The logical directory \fBplot\fR is +defined in the CL environment (file \fBlib$clpackage.cl\fR) as follows. +.DS + \fBset plot = "pkg$plot/"\fR + \fBset pkg = "iraf$pkg/"\fR +.DE +These definitions state that \fBplot\fR is a subdirectory of \fBpkg\fR, +and that \fBpkg\fR is a subdirectory of \fBiraf\fR, the root directory of +the IRAF system. The definition for the root directory is necessarily +both machine and configuration dependent. On a VAX/VMS system \fBiraf\fR +might be defined as follows: +.DS + \fBset iraf = "dra0:[iraf]"\fR +.DE +Recursively expanding the original VFN produces the following partially +machine dependent filename: +.DS + \fBdra0:[iraf]pkg/plot/graph.x\fR +.DE +The final, fully translated, machine dependent filename is produced by +folding the subdirectory names into the VMS directory prefix, mapping +the remaining filename (which does not change in this case), and concatenating: +.DS + \fBdra0:[iraf.pkg.plot]graph.x\fR +.DE +.PP +The important thing here is that while there may be many directories in the +system, \fIonly the definition of the IRAF root directory is machine +dependent\fR. Filenames in package script tasks, in the \fBhelp\fR database, +in makefiles, and so on inevitably include references to subdirectories, +hence the VFN syntax must recognize and map subdirectory references to fully +address the problems of machine independence. +.PP +Even when porting the system to another host running the same operating +system the root directory may (and usually will) change. +Since all logical directories and filenames are defined in +terms of the root directory, since the root is defined at runtime, and since +filenames are mapped at runtime, the system may be ported to another machine +running the same operating system by editing only one file, \fIwithout having +to recompile the system\fR. The importance of not having to recompile the +system becomes clear when the local hardware configuration changes or when +installing periodic updates at a site with multiple host computers all running +the same operating system. +.sp 0.08i +.TS +center; +cb s +n l. +Filename Mapping Primitives +.sp +zfsubd \&(osdir, subdir, new_osdir, maxch, nchars) fold subdir into osdir +zfxdir \&(osfn, osdir, maxch, nchars) get directory prefix +zfpath \&(osfn, pathname, maxch, nchars) get absolute pathname +.TE +.sp 0.08i +.PP +The primitives used to map filenames are shown above. The \fBzfsubd\fR +primitive folds a subdirectory name into a machine dependent directory +name (OSDIR), producing the OSDIR name of the subdirectory as output. +The subdirectory name ".." refers to the next higher directory in the +hierarchy, allowing upwards directory references. The form of a host +directory name is undefined, hence the primitive \fBzfxdir\fR is required +to extract a machine dependent directory prefix from a host filename (OSFN). +Directory expansion does not guarantee that the OSFN produced is independent +of the current working directory, hence the \fBzfpath\fR primitive is provided +to convert OSFNs into absolute pathnames. +.NH 4 +Filename Extensions +.PP +Filename \fBextensions\fR, like directories, pose a problem because different +operating systems use different extensions for the same logical file types. +Thus a Fortran source file might have the extensions ".f", ".f77", and +".for" on various systems. IRAF defines a standard set of file extensions +to be used in virtual filenames. Filename extensions are mapped by string +substitution when a file is referenced; unrecognized extensions are left alone. +The standard extensions used in IRAF virtual filenames are essentially those +used by UNIX, plus extensions for the special IRAF file types (e.g., +CL script files and parameter files). +.PP +To illustrate the mapping of filename extensions, consider the IRAF system +library \fBlib$libos.a\fR, which contains the kernel routines in object form. +On a UNIX system this might be expanded as "\fB/usr/iraf/lib/libos.a\fR", +whereas on a VMS system it might be converted to \fBdra0:[iraf.lib]libos.olb\fR. +.PP +The standard IRAF filename extensions are listed in the table below. +Those which are system dependent and are normally mapped are marked +at the right. +.sp +.TS +center box; +cb s s +c | c c +c | l c. +Standard Filename Extensions += +Extension Usage Mapped +_ +\\.a Library file (archive) ** +\\.c C language source ** +\\.cl Command Language script file +\\.com Global common declaration +\\.db database file +\\.e executable image ** +\\.f Fortran 77 source file ** +\\.h SPP header file +\\.hlp \fILroff\fR format help text +\\.ms \fITroff\fR format text +\\.o object module ** +\\.par CL parameter file +\\.pix pixel storage file +\\.x SPP language source +.TE +.sp +.PP +For the convenience of the user working interactively within the IRAF +environment, FIO permits virtual and host system dependent filenames +to be used interchangeably. Any arbitrary file or directory in the host +system may be referenced as an argument to an IRAF program, even if the +host directory has not been assigned a logical name. The filename mapping +scheme thus provides indirect support for pathnames, by permitting the +use of OS dependent pathnames to specify files when working interactively. +If a machine dependent filename is given, mapping of the root and extension +fields is disabled. +.NH 3 +Filename Mapping Algorithm +.PP +The primary requirement for filename mapping is that the process be +reversible, i.e., it must be possible to map a VFN to an OSFN and later +recover the original VFN by applying the reverse mapping. The following +additional requirements led to the selection of the algorithm described +in this section. +.sp 0.08i +.RS +.IP \(bu +There should be no efficiency penalty for simple filenames. +.IP \(bu +The algorithm must permit multiple processes to access the same directory +without contention. +.IP \(bu +The mapping must be transparent, i.e., reversible by inspection of the +host system directory, making it easy to work with directories and files +at the host system level. +.IP \(bu +The reverse mapping (OSFN to VFN) should be efficient, i.e., there must +not be a serious degradation of performance for template expansion and +directory listings. +.IP \(bu +The mapping should permit use of IRAF, with some loss of efficiency, +on a computer with a flat directory system. +.RE +.sp 0.08i +.PP +The algorithm selected consists of two phases. If the maximum information +content of a host system filename is sufficiently large, the first phase will +succeed in generating a unique mapping with no significant overhead. +If the first phase fails, the second phase guarantees a unique mapping on +any system with minimal overhead. The first phase maps the VFN into the +OSFN character set using \fBescape sequences\fR to map non-OSFN characters, +preserving the information content of a filename by increasing its length. +If the length of the OSFN thus generated exceeds the maximum filename length +permitted by the host system, the second phase accesses an \fBauxiliary +hidden file\fR to recover the excess information. +.PP +The operation of the mapping algorithm differs slightly depending on whether +an existing file is to be accessed or a new file is to be created. +The procedure followed to generate a unique OSFN when opening an existing +file is outlined below. The complications caused by multiple logical +directories, filename extensions, and transparency to machine dependent +filenames are not relevant to the algorithm and are omitted. +.sp +.DS +.cs 1 18 +\fBalgorithm\fR vfn_to_osfn +.sp .05 +\fBbegin\fR + # Phase one: encode VFN using only legal OSFN characters. +.sp 0.05i + map vfn to osfn using escape sequence encoding + \fBif\fR (length of osfn is within host limit) + \fBreturn\fR (osfn) +.sp 0.05i + # Phase two. Access or read auxiliary file to get OSFN. +.sp 0.05i + squeeze osfn to legal host filename length + \fBif\fR (squeezed osfn is degenerate) { + extract unique_osfn for named vfn from mapping file + \fBreturn\fR (unique_osfn) + } \fBelse\fR + \fBreturn\fR (osfn) +\fBend\fR +.DE +.cs 1 +.sp +.PP +\fBEscape sequence encoding\fR is a technique for mapping illegal characters +into sequences of legal characters. A single illegal character is mapped into +two legal characters. Strings of illegal characters, e.g., a sequence of +characters of the wrong case, are prefixed by a font change sequence. +For example, suppose the host system permits only upper case alphanumeric +characters in filenames. Lower case is the dominant case in VFNs, so case +will be inverted in the mapping and upper case in a VFN must be escaped. +If we pick the letter Y as our escape character, the following mappings might +be established (these are the defaults for VMS and AOS): +.sp 0.08i +.DS +.TS +ci ci ci +c c l. +vfn osfn usage +.sp +y Y0 the escape character itself +tolower Y1 switch to primary case +toupper Y2 switch to secondary case +\\_ Y3 underscore +\\. Y4 period +A-Z YA-YZ upper case letters +.TE +.DE +.sp 0.08i +.PP +The use of escape sequences can result in confusing mappings, +but if the escape character is chosen carefully such cases will be rare. +Most filenames are quite ordinary and will map with at most a case conversion. +Some examples of filenames which do not map trivially are given below. +The maximum length of a filename extension on the host system is assumed +to be 3 characters in these examples. +Any host limit on the maximum number of characters in the root is ignored. +For the purposes of illustration, we assume that the first character of the +OS filename cannot be a number, necessitating use of a no-op sequence. +.sp 0.08i +.in 0.8i +.TS +l l. +20 Y120 +02Jan83 Y102YJAN83 +Makefile YMAKEFILE +10.20.11 Y110Y420.11 +M92_data.Mar84 YM92Y3DATAY4YMAR84 +extract_spectrum.x EXTRACTY3SPECTRUM.X +_allocate Y3ALLOCATE +yy.tab.c Y0Y0Y4TAB.C +README Y2README +.TE +.in -0.8i +.sp 0.08i +.PP +Escape sequence encoding will probably suffice for most filename mapping, +particularly if the host system permits long filenames (e.g., AOS/VS currently +permits filenames of up to 32 characters). If the encoded filename is +too long for the host system, auxiliary files must be used to store the +excess information. A single \fBmapping file\fR is used for the entire +directory to permit efficient inverse mapping when expanding filename templates +and listing directories, and to avoid wasting disk space by generating many +small files. +.PP +If escape sequence encoding produces an OSFN longer than the maximum OS +filename length N, then characters must be discarded to produce an N character +filename. This is done by squeezing the long OSFN, preserving the first few +and final characters of the root, and the first character of the extension +(if any). For example, if the OSFN is CONCATENATE.PAR and N is 9, +the squeezed OSFN will be CONCATEEP.PAR (the mapping is the same as that +employed for long identifiers in the SPP, except that the first character +of the extension is appended). Once this is done, of course, the mapping +is no longer guaranteed to be unique. +.PP +More often than not a squeezed OSFN will be unique within the context +of a single directory. If this is the case it is not necessary to read the +mapping file to convert a VFN to an OSFN, although it is always necessary to +read the mapping file to carry out the inverse transformation. If the mapping +is unique within a directory, a null file with the same root name as the +primary file but with the (default) extension \fB.zmu\fR is created to +indicate that the mapping is unique (e.g., CONCATEEP.ZMU and CONCATEEX.ZMU). +If a second file is created with the same root OSFN and the mapping is no +longer unique, the \fB.zmu\fR directory entry is simply deleted, and the +mapping file will have to be read whenever either file is accessed. +.PP +The utility of the \fB.zmu\fR file is based on the assumption that the +determining the existence of a file is a much less expensive operation on +most systems than opening, reading, and closing the mapping file. +Furthermore, the \fB.zmu\fR file is a null length file, i.e., just an +entry in a directory, so no disk space is wasted. +Use of the advisory \fB.zmu\fR file does however involve an assumption that the +host system permits filename extensions. If this is not the case, +set the maximum length of a filename extension to zero in \fBconfig.h\fR, +and FIO will not generate the files. +.PP +The mapping file is effectively an extension of the directory and hence +will lead to contention problems when \fBconcurrent processes\fR try to +access the same directory. +A process must not be allowed to read the mapping file +while another process is modifying it, and under no circumstances may two +processes write to the mapping file at the same time. This requires that +a process which wishes to modify the mapping file place a lock on the +file before accessing it, and that a process be capable of waiting if the +mapping file is locked. The mapping data must not be buffered, i.e., +the file should be reread every time a (degenerate) file is accessed. +Fortunately contention should be rare since most file accesses to not +require use of the mapping file. +.PP +In summary, the overhead of the filename mapping algorithm should be +insignificant when (1) accessing files with simple names, +and (2) accessing files with long names for which the mapping is unique. +A small but fixed overhead is incurred when a file with a long name is +created or deleted, when a directory is read, and when a file is accessed +for which the mapping is degenerate. If the host computer has a decent +files system the algorithm will incur negligible overhead for all operations. + +.NH 2 +Directory Access +.PP +The capability to read filenames from a host directory is required for +the expansion of filename templates and for the directory listing program. +At the program interface level a directory appears to be a simple text file, +i.e., an unordered list of virtual filenames. A directory file is opened +at the applications level with \fBdiropen\fR and successive VFNs are read +with \fBgetline\fR. The driver procedures used to interface a directory to +FIO as a text file are machine independent. The kernel primitives called +to read OS filenames from a directory are machine dependent and are summarized +below. +.sp 0.08i +.TS +center; +cb s +n l. +Directory Access Primitives +.sp +zopdir \&(osfn, chan) open a directory +zcldir \&(chan, status) close directory +zgfdir \&(chan, osfn, maxch, status) get next OSFN +.TE +.sp 0.08i +.PP +Directory files are read-only and are accessed sequentially. +A single filename is returned in each call to \fBzgfdir\fR as a +packed string, returning as status the length of the string or EOF. +Filenames may be returned in any order; all filenames in the directory +should be returned (there are no "hidden" files at this level). +Raw OS dependent filenames should be returned. The inverse mapping +from OSFN to VFN is carried out in the machine independent code. +.PP +If the host system does not permit direct access to a directory file, +or does not provide a primitive which returns successive filenames, +it may be necessary to read the entire contents of the directory +into a buffer at \fBzopdir\fR time, returning successive filenames +from the internal buffer in \fBzgfdir\fR calls, and deleting the buffer +at \fBzcldir\fR time. + +.NH 2 +File Management Primitives +.PP +The kernel provides a number of general file management primitives for +miscellaneous operations upon files and directories. These are summarized +in the table below. These primitives are all alike in that they operate +upon files by name rather than by channel number. +The file management primitives +read, write, create, and delete the \fIdirectory entries\fR for files; +none access the actual file data. No primitive which operates upon a file +by name will be called while the file is open for i/o. +.sp 0.08i +.TS +center; +cb s +n l. +File Management Primitives +.sp +zfacss \&(osfn, mode, type, status) access file +zfchdr \&(new_directory, status) change directory +zfdele \&(osfn, status) delete a file +zfinfo \&(osfn, out_struct, status) get info on a file +zfmkcp \&(old_osfn, new_osfn, status) make null copy of a file +zfpath \&(osfn, pathname, maxch, status) osfn to pathname +zfprot \&(osfn, prot_flag, status) file protection +zfrnam \&(old_osfn, new_osfn, status) rename a file +.TE +.sp 0.08i +.PP +The \fBzfacss\fR primitive is used to determine whether or not a file +exists, is accessible with the given permissions, or is a text or binary +file. The \fBzfinfo\fR primitive returns a data structure defining the +file type, access modes, owner, size, creation date, time of last modify, +and so on. Information not returned includes whether the file is a text +or binary file, and whether or not the file is protected from deletion, +because this information is expensive to determine on some systems. +The \fBzfprot\fR primitive is called to place or remove delete protection +on a file, and to test whether or not a file is protected. +.PP +Primitives for accessing directories are limited to \fBzfpath\fR, which +returns the pathname of a file or of the current working directory, +and \fBzfchdr\fR, which changes the current directory. There are no +primitives for creating or deleting new subdirectories: thus far no program +has needed such a primitive. Directory creation and manipulation is +probably best left to the host system. +.PP +The assumption that there are only two basic file types, text and binary, +is overly simplistic when it comes to copying an arbitrary file. If an +executable file is copied as a binary file, for example, the copy will +not be executable. The problem is that a conventional binary file copy +operation copies only the file data: the directory entry is not copied, +and information is lost. The \fBzfmkcp\fR primitive makes a zero length +file which inherits all the system dependent attributes of another file, +excluding the filename, length, and owner. The new file is subsequently +opened for appending as either a text or binary file, and the file data +is copied in the conventional manner. Directory files cannot be copied. + +.NH 2 +Process Control +.PP +Process control, interprocess communication, exception handling, and error +recovery are probably the most complex and subtle services provided by the +IRAF virtual operating system, and the most likely to be machine dependent. +Despite the effort to isolate the machine dependence into the kernel and to +make the kernel primitives as simple and self contained as possible, a high +level understanding of the subtleties of process control may be necessary +to debug system problems. An introduction to process control in IRAF is +therefore presented to supplement the specifications for the kernel primitives. +It should be possible for a systems programmer implementing the kernel to +skim or skip most of this section, referring to it only to resolve ambiguities +in the kernel specifications. +.PP +The CL is currently the only process in the IRAF system which spawns other +processes. In this section we present an overview of process control in the +CL, defining important terms, discussing the conceptual model of a subprocess +as a command file, and describing the architecture of the process control +subsystem. The synchronous protocol for communicating with subprocesses +is discussed, as is the asynchronous file oriented protocol for communicating +with background jobs. The function of the IRAF main is described, including +error recovery and implementation strategies for interfacing asynchronous +processes to a host system window manager or job status terminal. The kernel +primitives for process control are presented at the end of the section. +.NH 3 +Overview and Terminology +.PP +From the point of view of the CL, an executable program is a \fBcommand file\fR +containing a sequence of commands to be parsed and executed. Once opened or +\fBconnected\fR, a compiled program is equivalent to a script task or the +user terminal; the CL does not know or care where the commands are coming from. +Any CL command that can be executed from the user terminal can also be executed +by a compiled program or a script task. Calls to external programs, script +tasks, the terminal, or any other \fBlogical task\fR may be nested until the +CL runs out of file descriptors or stack space. +.PP +A \fBprogram\fR is a compiled logical task. An arbitrary number of programs +may be linked together to form a single physical \fBprocess\fR, i.e., +executable file. When an IRAF process is executed the \fBIRAF Main\fR +(main routine or driver procedure) in the subprocess initializes the process +data structures and then enters an interpreter loop awaiting a command from +the input file, which may be an IPC file (the CL), a terminal, or a text file. +Hence, not only does a subprocess look like a file to the CL, the CL looks +like a file to a subprocess. +.PP +Task termination occurs when the CL reads either end of file (EOF) or the +command \fBbye\fR. When a script task terminates the script file is closed; +when a program terminates the associated process may be \fBdisconnected\fR. +A subprocess is disconnected by sending the command \fBbye\fR to the +IRAF Main in the subprocess. The IRAF main cleans up the files system and +dynamic memory, calls any procedures posted by the user program with +\fBonexit\fR, and then returns to its caller (the \fBprocess main\fR), +causing process exit. +.PP +When a process spawns a subprocess, the original process is called +the \fBparent\fR process, and the subprocess is called the \fBchild\fR process. +A parent process may have several child processes, but a child process may +have only a single parent, hence the process structure of IRAF is a rooted +tree. The root process is the interactive CL; the user terminal (or a window +manager process) is the parent of the CL. Since the CL is the only process +which spawns other processes, an IRAF process tree has only two levels. +.PP +Processes communicate with each other only via \fBIPC channels\fR or +ordinary disk files. Since the only way to open an IPC channel is to connect +a subprocess, a process may communicate only with its parent or with one of its +children. Separate channels are used for reading and writing, rather than +a single read-write channel, to conform to the logical model of a subprocess +as a text file and to facilitate record queueing in the write channel. +Since only IPC channels and ordinary files are used for interprocess +communication and synchronization, the parent and child processes may reside +on separate processors in a \fBmultiple processor\fR system configuration. +.PP +The IPC channels are used to pass commands, data, and control parameters. +While a program is executing it sends commands to the CL, +most commonly to get or put the values of \fBCL parameters\fR. +In a get parameter operation the CL responds by printing the value of the +parameter on its output, just as it does when the +same command is typed in interactively. The output of the CL is connected +to the input IPC channel of the child process, which reads and decodes the +value of the parameter, returning the binary value to the calling program. +.PP +The standard input, standard output, standard error output, standard graphics +output, etc. of the child process are known as \fBpseudofiles\fR because the +actual files are opened and controlled entirely by the CL. A read or write +to a pseudofile in the child process is converted into a command to read or +write a binary block of data and sent over the IPC channel along with the +binary data block. Hence, even though most traffic on the IPC channels is +ASCII text, the channels are implemented as binary files. A large transfer +block size is used for all pseudofiles except STDERR to maximize throughput. +All parameter i/o and pseudofile i/o is multiplexed into a single command stream +and transmitted over the two IPC channels. +.PP +To minimize process connects, the CL maintains a \fBprocess cache\fR of +connected but generally idle subprocesses. A process is connected and placed +in the cache when a program in that process is run. A process will remain +in the cache, i.e., remain connected to the CL process, until either a new +process connect forces the process out of the cache or until the cache +is flushed. Since programs execute serially, at most one cached process will be +active at a time. Since several subprocesses may simultaneously be connected +and each subprocess may contain an arbitrary number of programs, the cache +can greatly reduce the average overhead required to run an external program, +while permitting dynamic linking of programs at run time. +.PP +The CL executes programs serially much as a program executes subroutines +serially. The protocol used to communicate with a connected subprocess is +synchronous. To execute a program or general command block as a +\fBbackground job\fR, i.e., asynchronously, the CL spawns a copy of itself +which executes independently of the parent CL. The child CL inherits the +full interior state of the parent CL, including the metacode for the command +to be executed, all loaded packages and parameter files, the environment list, +and the dictionary and stacks. Open files are not inherited, the child CL +is not connected to the parent CL, and the CL subprocess is not placed in the +process cache of the parent. The child CL manages its own process cache +and executes external programs using the synchronous protocol. A child CL +may spawn a child CL of its own. +.NH 3 +Synchronous Subprocesses +.PP +The sequence of actions required to synchronously execute an external compiled +program are summarized below. Only those actions required to execute a +program are shown; the process cache is a local optimization hidden within the +CL which is not relevant to a discussion of the synchronous subprogram protocol. +Everything shown is machine independent except the process main and the +IPC driver. Only a single process may be in control at any one time. +.PP +A process may be spawned by another IRAF process or by the host command +interpreter or JCL. When a process is spawned the host system transfers +control to a standard place known as the \fBprocess main\fR. The process +main must determine what type of parent it has and open the type of input +and output channels required by the parent. If the parent is another process +IPC channels are opened. The process main then calls the IRAF Main which +initializes IRAF i/o and enters the Main interpreter loop to read commands +from the parent. +.sp 0.05i +.DS +.ce +\fBProcess Startup\fR +.sp +\fIParent process:\fR + spawn the subprocess + open IPC channels between parent and child + send commands to initialize environment list in child +.sp +\fIProcess Main in child:\fR + determine whether input device is a terminal, text file, or process + open input and output channels (e.g. the IPC channels) + call the IRAF Main +.sp +\fIIRAF Main in child:\fR + save process status for error restart + initialize file i/o, dynamic memory, and error handling + post default exception handlers + enter interpreter loop, reading commands from parent +.DE +.sp +.PP +An IRAF process will interpret and execute successive commands in its input +file until it encounters either EOF or the command \fBbye\fR. The first +block of commands read by the Main will normally be a sequence of \fBset\fR +statements initializing the process environment (defining logical directories +and devices, etc.). A number of calls to the programs resident in the process +will normally follow. When a program runs it assumes control and begins +issuing commands to the parent to read and write parameters and pseudofiles. +The IRAF i/o system is reset to its default initial state each time a program +terminates (this can be overridden by a program if desired). +.sp +.DS +.ce +\fBProcess Execution\fR +.sp +\fIParent process:\fR + send name of program to be run to the IRAF Main in the child process + redirect command input to child, i.e., transfer control to the + program in the child process +.sp +\fIProgram in child process:\fR + (we were called by the interpreter in the IRAF Main) + execute, sending commands to parent to read and write parameters + and pseudofiles + return to caller (the IRAF Main) when done +.sp +\fIIRAF Main:\fR + flush STDOUT, close any open files, etc. + send the command \fBbye\fR to parent to signal that program has + completed and to return control to the parent + enter interpreter loop, reading commands from parent +.DE +.sp +.PP +A process shuts down or exits only when commanded to do so by the parent, +i.e., when an input file read returns EOF or the command \fBbye\fR is executed. +Any user defined procedures posted with \fBonexit\fR calls during process +execution will be executed during process shutdown. Control eventually +returns to the process main which takes whatever system dependent actions +are required to terminate the process. +.sp +.DS +.ce +\fBProcess Shutdown\fR +.sp +\fIParent process:\fR + if no further programs are to be run, send the command \fBbye\fR + to the child to initiate process shutdown +.sp +\fIIRAF Main:\fR + disable IPC output (parent is no longer reading) + call any user procedures posted with \fBonexit\fR, i.e., flagged + to be executed upon process shutdown + return to caller (the process main) when done +.sp +\fIProcess Main:\fR + terminate subprocess +.sp +\fIParent process:\fR + disconnect child process +.DE +.sp 0.05i +.PP +If a process issues a read request on an IPC channel +and there is no input in the channel, the reading process will block, hence +reading from an empty IPC channel causes process synchronization. Successive +writes are queued until the channel is full, hence writing is generally +asynchronous and some degree of overlapped execution is possible. +Traffic on the IPC channels is restricted to the small set of commands +described in the next section. +.NH 3 +Standard IPC Commands +.PP +Although in principle a subprocess may send any legal CL command to the CL +process, in practice only a small subset of commands are permitted in order to +minimize the size of the interface. A larger interface would mean more +dependence upon the characteristics of a particular CL, making it more +difficult to modify the CL and to support several different versions of the CL. +.PP +The IPC interface commands described in this section are a high level protocol +implemented entirely above the kernel routines to support execution of external +programs by the CL. If the parent process were not the CL and if a new IRAF +Main were implemented (the IRAF Main is an ordinary SPP procedure), then a +quite different protocol could be devised. +.PP +The \fBIRAF Main requests\fR, i.e., the IPC commands sent to the IRAF Main by +the parent process, are shown below in the order in which they are normally +sent to the child process. Italicized text denotes dummy parameters to be +replaced by the name or value of the actual parameter when the command is +issued. Keywords are shown in boldface. Optional characters or arguments are +delimited by square brackets. All commands are ASCII lines of text terminated +by the \fBnewline\fR character. +.sp +.in 1.0i +.KS +.ti -0.5i +\fBset\fR \fIvariable\fR = \fIstring\fR +.ti -0.5i +\fBset\fR @\fIfname\fR +.sp 0.04i +Set the value of an environment variable or set the environment from a file. +If the variable does not exist it is created; if it does exist the new value +silently replaces the old value. The \fBset\fR statement is used to pass +the environment list of the parent to the child process when the subprocess +is connected. The second form of the \fBset\fR statement reads a list of +\fBset\fR declarations from a text file, and is especially useful in debug +mode. +.KE +.sp +.KS +.ti -0.5i +\fB?\fR +.sp 0.04i +Print the names of all user programs linked into the process in tabular +form (i.e. print a menu) on the standard output. This command is not +currently used by the CL; it is most useful when debugging a process run +directly from the host command interpreter. +.KE +.sp +.ti -0.5i +[\fB$\fR] \fIprogram\fR [<[\fIfname\fR]], [[\fIstream\fR[(T|B)]]>[\fIfname\fR]], [[\fIstream\fR]>>[\fIfname\fR]] +.sp 0.04i +Execute the named program. The environment should have been initialized by +the time a program is run. If a dollar sign is prefixed to the command +name, the cpu and clock time consumed by the process are printed on the +standard error output when the task terminates. +If a pseudofile stream has been redirected by the parent or is to be +redirected by the child, this should be indicated on the command line. +Thus the IRAF Main command +.DS +count < +.DE +would run the program \fIcount\fR, informing the IRAF Main that the standard +input has already been redirected by the parent (some programs need to know). +If redirection to or from a named file is indicated, the IRAF Main will open +the file and redirect the indicated stream before running the program. +Pseudofiles streams are denoted by the numerals 1 through 6, corresponding +to STDIN, STDOUT, STDERR, STDGRAPH, STDIMAGE, and STDPLOT. If output is +being redirected into a new file and \fBT\fR or \fBB\fR appears in the +argument (e.g., "4B>file"), a text or binary file will be created as specified. +If the file type suffix is omitted and the output stream is STDOUT or STDERR +a text file will be created, otherwise a binary file is created. +For example, the command +.DS +count <, > file +.DE +directs the Main to flag the standard input as redirected, open the new text +file "file" as the standard output of the the program \fIcount\fR, and then +run the program. When the program terminates the Main will automatically +close the output file. +.sp +.KS +.ti -0.5i +\fBbye\fR +.sp 0.04i +Commands the subprocess to shutdown and exit. The subprocess must not read +or write the IPC channels once this command has been received; the CL +disconnects the subprocess immediately after sending \fBbye\fR. User +procedures posted with \fBonexit\fR are called during process shutdown. +If an irrecoverable error occurs during normal process shutdown it will cause +an immediate \fBpanic shutdown\fR of the process. The kernel writes an +error message to the process standard error channel (e.g. the user terminal) +when a panic shutdown occurs. +.KE +.sp +.in -1.0i +.PP +Although it might appear that initialization of the process environment +list via a sequence of \fBset\fR commands is inefficient, +the \fBset\fR commands are buffered and transmitted to the child process +in large binary IPC blocks to minimize the overhead. +The amount of data transmitted is not significantly +different than it would be if the environment list were transmitted as a +binary array, and matching of the internal environment list data structures in +the two processes is not required. Furthermore, the \fBset\fR command is +ideal when debugging a process or when running a process in batch mode +with a previously prepared command input file. +.PP +The IRAF Main commands are used both by the CL to run an external compiled +program and by the programmer when debugging a process at the host system +level. The IRAF Main knows whether it is being used interactively or not, +and modifies the interface protocol slightly when used interactively to +provide a better user interface. For example, the Main issues a command +prompt only when being used interactively. The form of a parameter request +and of a pseudofile read or write request is also slightly different in the +two cases. +.PP +The \fBCL requests\fR, i.e., the commands sent by a running program to the CL +(or any other parent process) are shown below. These are the only commands +which the child can legally send to the parent, and hence the only commands the +interpreter in the parent need recognize. The noninteractive syntax is shown. +If the parent process is not the CL a completely different protocol can be +used. When a subprocess is run interactively the \fBxmit\fR and \fBxfer\fR +requests are omitted (only the data is sent) and the newline is omitted after a +parameter read request. +.sp +.in 1.0i +.KS +.ti -0.5i +\fIparam\fR = +.sp 0.04i +The parent process is directed to print the single-line value of the +named parameter in ASCII on the child's input IPC channel. The child +decodes the response line and returns a binary value to the program. +.KE +.sp +.KS +.ti -0.5i +\fIparam\fR = \fIvalue\fR +.sp 0.04i +The parent process is directed to set the value of the named parameter to +the indicated ASCII value. The child does not expect a response, and parameter +write requests may be queued in the output IPC channel. +.KE +.sp +.KS +.ti -0.5i +\fBxmit\fR (\fIpseudofile\fR, \fInchars\fR) +.sp 0.04i +The parent process is directed to read exactly \fInchars\fR chars of binary +data from the IPC channel and transmit it without interpretation to the +indicated pseudofile. The child does not expect a response, and pseudofile +write requests may be queued in the output IPC channel. Pseudofiles are +denoted by the numerals 1 through 6, corresponding to STDIN, STDOUT, STDERR, +STDGRAPH, STDIMAGE, and STDPLOT. +.KE +.sp +.KS +.ti -0.5i +\fBxfer\fR (\fIpseudofile\fR, \fImaxchars\fR) +.sp 0.04i +The parent process is directed to read up to \fImaxchars\fR chars of binary +data from the indicated pseudofile and transmit it without interpretation to the +input IPC channel of the child. The binary data block should be preceded +by an ASCII integer count of the actual number of chars in the data block. +.KE +.sp +.KS +.ti -0.5i +\fBbye\fR +.sp 0.04i +Normal program termination. Control is transferred from the +child to the parent. The child returns to the interpreter loop in the +IRAF Main, awaiting the next command from the parent. +.KE +.sp +.KS +.ti -0.5i +\fBerror\fR (\fIerrnum\fR, "\fIerrmsg\fR") +.sp 0.04i +Abnormal program termination. +An irrecoverable error has occurred during the execution of the program +(or of the IRAF Main), and the CL is directed to take an error action for +error number \fIerrnum\fR. The child returns to the interpreter loop in the +IRAF Main, awaiting the next command from the parent. If the error is not +caught and handled by an error handler in a CL script, the error message +\fIerrmsg\fR is printed on the standard error output of the CL and the +child process is commanded to shutdown. +.KE +.sp +.in -1.0i +.NH 3 +Example +.PP +By this point process control probably sounds much more complicated than it +actually is. A brief example should illustrate the simplicity +of the CL/IPC interface. Consider the CL command +.DS +cl> \fBset | match tty\fR +.DE +which prints the values of all environment entries containing the substring +\fBtty\fR. The CL task \fBset\fR is a builtin function of the CL and +hence does not use the IPC interface. We assume that the process +\fBsystem$x_system.e\fR, which contains the program \fImatch\fR, +has already been connected so that it is not necessary to pass the +environment to the child. The traffic over the IPC channels is shown below. +If a running IRAF system is available the process side of this example can +be duplicated by typing \fBecho=yes\fR followed by the command shown above. +.sp 0.08i +.TS +center; +ci ci +l l. +CL Process +.sp +\fBmatch <\fR + \fBpattern=\fR +tty + \fBmetacharacters=\fR +yes + \fBstop=\fR +no + \fBxfer(1,1024)\fR +1024 +\fI(1024 chars of data sent to child)\fR + \fBxfer(1,1024)\fR +368 +\fI(368 chars of data sent to child)\fR + \fBxmit(2,63)\fR + \fI(63 chars of data sent to CL)\fR + \fBbye\fR +.TE +.sp 0.08i +.PP +Each line of text shown in the example is transmitted through the appropriate +IPC channel as a single distinct record. Commands are shown in boldface. +The italicized records represent raw data blocks. +The process \fBsystem$x_system.e\fR contains the fifty or so executable programs +in the \fBsystem\fR package and hence is a good example of the use of +multitasking and the process cache to minimize process connects (as well as +disk space for executable images). +.NH 3 +Background Jobs +.PP +IRAF process control does not support fully asynchronous subprocess execution +for the following reasons: +.sp +.RS +.IP \(bu +The parent and child processes are tightly bound, i.e., while an external +program is executing the CL process is subservient to the applications program. +The fact that the CL is a separate process is an irrelevant detail to +the applications program. From the point of view of the applications program +the CL is a database interface called by CLIO. Applications programs are not +fully functional unless connected to a CL at run time, and a synchronous, +interactive interface between the two processes is assumed. +.IP \(bu +From the point of view of the user or of a CL script, external programs are +subroutines. Subroutines execute serially in the context of the calling +program. In this case the context is defined by the state of the data +structures of the CL, i.e., the dictionary, environment list, loaded packages +and tasks, parameters, and so on. +.IP \(bu +The user does not care whether a task is a subprocess or a CL script, +and tends to think in terms of \fBcommand blocks\fR rather than individual +commands. A command block is a user specified sequence of commands to +be compiled and executed as a single unit. Asynchronous subprocesses +are not interesting; what we want is an asynchronous command block. +.IP \(bu +It is much more difficult to define a machine independent process control +interface for asynchronous subprocesses than for synchronous subprocesses. +The problem is similar to that of designing +a multiprocessing operating system, with the CL acting as the operating system +kernel and the user as the cpu. Asynchronous subprocess execution is +inconsistent with the conceptual model of subprocesses and users as command +files, and is extremely difficult to implement in a portable system in any case. +.RE +.sp +.PP +For these and other reasons, background job execution is implemented in +IRAF by spawning a copy of the foreground CL which executes as a +\fBdetached process\fR, rather than as a connected subprocess. +The child CL manages its own process cache independently of the parent. +All connected subprocesses execute synchronously, i.e., only one process +in a tree of connected processes may be active at a time. +Since the child is never connected to the parent, the background CL may +execute at any time (e.g. in a batch queue), and may continue to execute +after the parent process has terminated (if the host system permits). +.PP +The child CL inherits the data structures of the parent as they existed +immediately after translating the command block into metacode and just prior +to execution. The parent's data structures are propagated to the child by +writing them into a binary file which is subsequently opened and read by the +child. Open files are not inherited. The command block executes in the +child in exactly the same context as it would have had if executed in the +parent, with exactly the same results. +.PP +On many systems background job execution will be predominantly noninteractive, +particularly if background jobs are placed into a batch queue. +Even if a background job is run noninteractively, however, there is no +guarantee that the job will not require interaction during execution, +for example if the user forgot to set the value of a parameter when the +job was submitted. Rather than aborting a background job which needs to +query for a parameter, the CL provides a limited but portable method for +servicing queries from background jobs. Extensive interaction with background +jobs is beyond the capabilities of the portable IRAF system but is not ruled +out; interfacing to \fBwindow management\fR facilities is straightforward +if the host system provides such facilities, and is described in the next +section. +.PP +The CL has a builtin capability for generating and servicing queries from +noninteractive background jobs. Such queries might be normal and expected, +e.g. if a background job executes concurrently with the interactive CL and +limited interaction is desired, or might be a failsafe, e.g. if a background +job has consumed several hours of cpu time and the job would have to be +resubmitted if it were to abort because it could not satisfy a parameter +request. +.PP +The CL will automatically initiate a query request sequence whenever it is +executing in the background and an attempt to service a query by reading +from the process standard input returns EOF. To initiate a query request +the CL writes the query prompt into a \fBservice request file\fR, +writes a status message to the standard error output of the CL process +noting that the job is stopped waiting for parameter input, +and enters a loop waiting for the \fBquery response file\fR to be created. +When the query response file becomes accessible the CL opens it, +reads the contents to satisfy the original read request, +deletes the file, and continues normal execution. +.PP +If there is no response the background CL will eventually timeout, writing +a fatal error message to the standard error output of the process. +Queries from background jobs are normally satisfied from an interactive CL +using the builtin task \fBservice\fR, which types the service request file +on the terminal, deletes the file, reads the user's response from the terminal, +and writes the response into the query response file. +If the query response is unacceptable another query will be generated and +the interchange is repeated. The use of simple text files for interprocess +communication makes the technique very general, and in principle there is +nothing to prevent the technique from being used to service requests from +jobs run either as detached subprocesses or in batch queues. +.NH 3 +The Process and IRAF Mains +.PP +The roles of the Process and IRAF Mains should already be clear from the +previous sections. The process main is machine dependent and is called by +the host operating system when a process is executed. The IRAF Main is +a portable SPP procedure which is called by the process main +during process startup, which acts as a simple command interpreter during +process execution, and which returns control to the process main during +process shutdown. +.NH 4 +The Process Main +.PP +The \fBprocess main\fR is a part of the kernel, but unlike any other kernel +procedure it is not Fortran callable (in fact it is not necessarily a procedure +at all). The process main further differs from any other kernel procedure +in that it calls a high level procedure, the IRAF Main. Since the process +main is not Fortran callable, however, there is no possibility of recursion. +.PP +The primary functions of the process main are to open and initialize the +process i/o channels and to call the IRAF Main. +The process i/o channels are the standard input, standard output, and standard +error output of the process. The process or device to which the channels +are connected is both system dependent and dependent on how the process was +spawned. +.PP +The process main can be coded in assembler if necessary on almost any system. +Typically the host operating system will upon process entry transfer control +to a predefined address or external identifier, and this entry point should be +the process main. On many modern systems it will be possible to code the +main in a high level language; this is desirable provided the high level +language does not have a main of its own which necessitates loading a lot +of extraneous code which will never be used (since IRAF does all i/o via +the IRAF kernel). On a UNIX system, for example, the process main is +implemented as the C procedure "main" with no overhead, so there is nothing +to be gained by coding the process main in assembler. +.sp +.cs 1 18 +.nf +\fBprocedure\fR process_main + +input_chan: process standard input channel +output_chan: process standard output channel +errout_chan: process standard error output channel + +\fBbegin\fR + # Determine type of output device and connect channels. + + \fBif\fR (we are a connected subprocess) { + connect input_chan and output_chan to IPC channels + connect errout_chan to the user terminal (i.e, to the + standard error output channel of the parent process) + + } \fBelse if\fR (we are a detached process) { + \fBif\fR (window management facilities are available) + connect all channels to window manager + \fBelse\fR { + connect input_chan such that a read will return EOF + \fBif\fR (we are executing in a batch queue) + connect output channels to files + \fBelse\fR { + connect both output_chan and errout_chan to the user + terminal (i.e, to the standard error output channel + of the parent process, if the terminal can be + written to by multiple processes) + } + } + + } \fBelse if\fR (we were run from the host command interpreter) { + \fBif\fR (we were called interactively) + connect channels to user terminal + \fBelse\fR { + connect input_chan and output_chan to job input + and output files, and errout_chan to operator + console or system dayfile. + } + } + + # Call the IRAF Main, the command interpreter or driver of an + # IRAF process. + + call iraf_main, passing the channel numbers and identifying + the driver and protocol to be used by the Main + + # We get here only after the parent has commanded the IRAF + # Main to shutdown, and after shutdown has successfully + # completed. Fatal termination occurs elsewhere. + + close channels if necessary + normal exit, i.e., terminate process +\fBend\fR +.cs 1 +.fi +.sp +.PP +An IRAF process may easily be used in a completely batch mode by connecting +the process channels to text files. On an interactive system the channels +of a detached process may be connected directly to the user terminal, +but it can be annoying for the user if background processes are intermittently +writing to the terminal while the user is trying to do something else +(e.g. trying to edit a file using a screen editor). Having multiple processes +trying to simultaneously read from a terminal is disastrous. +.PP +The best solution to the problem of multiple processes trying to read or +write from the user terminal is some sort of \fBwindow manager\fR. +A simple window manager which can handle output from multiple simultaneous +IRAF processes but which will only allow a single process to read is not +difficult to code on many systems, provided one admits that the capability +is machine dependent. A full up window manager such as is provided on many +modern microcomputers is a much more difficult problem and should not be +attempted as an add-on, as it really needs to be integrated into the +operating system to work well. A better approach is to buy a microcomputer +which comes with a bit-mapped terminal and a fully integrated window manager, +or to buy a smart terminal which has window management capabilities. +.PP +If a window manager is to be provided as an add-on to a system which does +not already have one, it should be implemented as a single process handling all +i/o to the terminal. The CL will be run from the window manager process +and detached processes will talk directly to the window manager process +using multiplexed IPC channels. Such an add-on window manager is unlikely +to be fully usable for non-IRAF processes (e.g. the host system screen editor) +unless the host operating system is modified in fundamental ways. If the +window manager has to be built from scratch consider coding it as an IRAF +process with an extended system interface, so that it will be at least +partially portable. +.NH 4 +The IRAF Main +.PP +The IRAF Main is the "main program" of an IRAF process. The primary function +of the Main is to interpret and execute commands from the standard input +of the CL process (the stream CLIN) until either EOF is seen or the command +\fBbye\fR is received by the Main (as opposed to a program called by the Main). +The Main is mechanically generated by the SPP compiler when the \fBtask\fR +statement is encountered in an SPP program; the source is in the file +\fBmain.x\fR in the logical directory \fBsys$system\fR. +.PP +The secondary functions of the Main are to initialize the IRAF i/o system +and participate in error recovery. The first time the Main is called it +initializes the i/o system and posts a default set of \fBexception handlers\fR. +The Main can only be called again (without recursion) during an \fBerror +restart\fR. If an irrecoverable error occurs during error restart, +a panic exit occurs, i.e., the process dies. +.PP +Error restart takes place when a uncaught hardware or software exception +occurs, or when an error action is taken by a program and no user +\fBerror handler\fR is posted. All exceptions and errors may ideally be +caught and processed by a user exception handler or error handler, without error +restart occurring. When error restart occurs the hardware stack is +reset and control transfers to the marked position within the process main. +The process main calls the IRAF Main, which knows that it has been called +during error restart. +.PP +When the IRAF Main is called during error restart the first thing it does +is call any user procedures posted with \fBonerror\fR. If an irrecoverable +error occurs during execution of an \fBonerror\fR error recovery procedure, +\fBerror recursion\fR occurs and a panic exit results. +When the \fBonerror\fR procedures have successfully executed the Main sends +the \fBerror\fR statement to the CL (i.e., to the stream CLOUT) and reenters +its interpreter loop, awaiting the next command from the CL. +If no user error handler is posted at the CL level (error handling was not +implemented at the CL level at the time when this was written), then the +CL will direct the child process to shutdown to ensure that dynamic memory +space is reclaimed, and to ensure that a user program is not left in a bad +state by the error. +.NH 3 +Process Control Primitives +.PP +We are now in a position to define and understand the kernel primitives +necessary to implement process control. There are 9 such primitives, +excluding the process main and the exception handling primitives. +The mnemonic "pid" refers to the \fBprocess id\fR, a unique magic integer +assigned by the host operating system at process creation time. +.sp 0.08i +.TS +center; +cb s +n l. +Process Control Primitives +.sp +zopcpr \&(process, inchan, outchan, pid) open connected subprocess +zclcpr \&(pid, exit_status) close connected subprocess +zintpr \&(pid, exception, status) interrupt connected subprocess +.sp +zopdpr \&(process, bkgfile, jobnum) open or queue detached process +zcldpr \&(jobnum, killflag, exit_status) close or dequeue detached process +.sp +zgtpid \&(pid) get process id of current process +zpanic \&(errcode, errmsg) panic exit +zsvjmp \&(jumpbuf, status) save process status +zdojmp \&(jumpbuf, status) restore process status +.TE +.sp 0.08i +.PP +Separate sets of primitives are defined for connected and detached +subprocesses. A subprocess is connected with \fBzopcpr\fR, which spawns +the subprocess and opens the IPC channels. The child is assumed +to inherit the current working directory of the parent. +Connection of the IPC channels to FIO and transmission of the environment +list to the subprocess is left to the high level code. +.PP +A connected subprocess is disconnected with \fBzclcpr\fR, which waits +(indefinitely) for the subprocess to exit and returns the exit status code, +e.g. OK. The high level code must command the subprocess to shutdown +before calling \fBzclcpr\fR or deadlock will occur. +The high level code guarantees that \fBzclcpr\fR will be called to close +any subprocess opened with \fBzopcpr\fR. The \fBzintpr\fR primitive +raises the interrupt exception X_INT in a connected subprocess. +.PP +A detached process is spawned or submitted to a batch queue with \fBzopdpr\fR. +It is up to \fBzopdpr\fR to pass the name of the background file on to the +child by some means (the background file tells the detached process what +to do). A detached process may be killed or removed from the batch queue +by a call to \fBzcldpr\fR. The high level code will call \fBzcldpr\fR if +the detached process terminates while the parent is still executing, +but there is no guarantee that a detached process will be closed. +.PP +The remaining primitives are used by all processes. The \fBzgtpid\fR primitive +returns the process id of the process which calls it; this is useful for +constructing unique temporary file names. The \fBzpanic\fR primitive is +called when error recovery fails, i.e., when an error occurs during error +recovery, causing error recursion. A panic shutdown causes immediate process +termination, posting an error message to the process standard error output +and returning an integer error code to the parent process. +.PP +The IRAF main calls \fBzsvjmp\fR to save the process control status for +error restart. The process status is saved in \fIjumpbuf\fR, +allowing several jump points to be simultaneously defined. +A subsequent call to \fBzdojmp\fR restores +the process status, causing a return from the matching \fBzsvjmp\fR call +\fIin the context of the procedure which originally called \fBzsvjmp\fR. +The \fIstatus\fR argument is input to \fBzdojmp\fR and output by \fBzsvjmp\fR, +and is zero on the first call to \fBzsvjmp\fR, making it possible for the +procedure which calls \fBzsvjmp\fR to determine how it was entered. +These extremely machine dependent routines are patterned after the UNIX +\fBsetjmp\fR and \fBlongjmp\fR primitives, but are Fortran callable. +They will almost certainly have to be written in assembler since they fiddle +with the hardware stack and registers. +.PP +On a \fBmultiple processor\fR system it should be possible to spawn both +connected and detached processes on a remote processor. For example, +if the parent process resides on a diskless node in a cluster, it may be +desirable to run subprocesses that do heavy i/o on the remote file server +processor which has a high i/o bandwidth to disk. On such a system +advice on the optimal processor type should be encoded as extra information +in the process file name passed to \fBzopcpr\fR or \fBzopdpr\fR; this will +require modification of the CL \fBtask\fR statement for such processes. + +.NH 2 +Exception Handling +.PP +An exception is an asynchronous event, i.e., an interrupt. +Typical \fBhardware exceptions\fR are an attempt to access an unmapped region +of memory, illegal instruction, integer overflow, or divide by zero. +A hardware exception occurs when the hardware detects an error condition +while executing a hardware instruction. Typical \fBsoftware exceptions\fR are +interrupt and kill. A software exception occurs when a program, e.g., +the terminal driver or an applications program like the CL, sends an +interrupt or \fBsignal\fR to a process. When an exception occurs program +execution is interrupted and control transfers to an \fBexception handler\fR, +i.e., to a previously posted system or user procedure. +.PP +The ability to post an exception handler or to send a signal to a process +is fundamental in any multiprocessing operating system, but regrettably there +are still some older systems that do not make such facilities available to +applications code. Hopefully yours is not such a system. Even if a system +provides exception handling facilities, the set of exceptions defined for +a particular computer or operating system is very system dependent. +Exception handling can be subtly machine dependent, e.g., it is not always +possible to disable an exception, and it is not always possible to resume +program execution (restart the interrupted instruction) following an exception. +.PP +The IRAF Main posts a default set of exception handlers during process startup. +All host system exceptions that might occur during the execution of an IRAF +process should be catchable by one of the default exception handlers. +If an exception is not caught and is instead handled by the host, +the process will almost certainly die without the knowledge of the CL, +leading at best to a cryptic "write to a subprocess with no reader" error +message, and at worst to deadlock. Since error recovery and process shutdown +will be skipped if an uncatchable exception occurs, disk data structures may +be corrupted. +.PP +The virtual system recognizes only four classes of exceptions; all possible +host system exceptions should either be mapped into one of these exceptions +or caught in the kernel and mapped into ERR. +.sp 0.08i +.TS +center box; +cb s s +ci | ci | ci +l | c | l. +Virtual Machine Exceptions +_ +exception code meaning += +X_ACV 501 access violation +X_ARITH 502 arithmetic error +X_INT 503 interrupt +X_IPC 504 write to IPC with no reader +.TE +.sp 0.08i +.PP +The largest class of exceptions on many systems will be the access violations. +This class includes such things as illegal memory reference, illegal +instruction, illegal system call, and so on. +Arithmetic exceptions include divide by zero, integer overflow, and the like. +Interrupt is the exception raised by \fBzintpr\fR or by the +host system terminal driver when the interrupt sequence is typed (e.g. ctrl/c). +.sp 0.08i +.TS +center; +cb s +n l. +Exception Handling Primitives +.sp +zxwhen \&(exception, handler, old_handler) post an exception +zxgmes \&(os_exception, outstr, maxch) get OS code and message +.TE +.sp 0.08i +.PP +An exception handler is posted for a virtual exception with the primitive +\fBzxwhen\fR. All host exceptions in the indicated virtual exception class +are affected. The argument \fIhandler\fR is either the entry point address +of the new user exception handler or the magic value X_IGNORE (null). +The address of the old handler or X_IGNORE is returned as the third argument, +making it possible for the high level code to chain exception handlers +or repost old exception handlers. The calling sequence for a user exception +handler is as follows: +.DS +user_handler (exception, next_handler) +.DE +.LP +The user exception handler is called with the integer code for the actual +virtual exception as the first argument. The integer code of the last +machine exception and a packed character string describing the exception +may be obtained by a subsequent call to \fBzxgmes\fR. A program which uses +a machine exception code is machine dependent, but machine exception codes +can be parameterized and some programs need to know. +The CL, for example, has to be able to recognize the machine exception for +a write to a process (an IPC channel) with no reader. +.PP +When an exception occurs control actually transfers to the \fBkernel exception +handler\fR, which maps the machine exception into a virtual exception, +looks at the kernel exception table to determine what type of action is +required, and then calls the user exception handlers. A user exception +handler is expected to handle the exception in some application dependent +way and then either change the process context by calling \fBzdojmp\fR or +\fBzrestt\fR, or return control to the kernel exception handler. +If control returns to the kernel handler the output argument \fBnext_handler\fR +will contain either the entry point address of the next exception handler +or X_IGNORE. The high level code assumes that once an exception handler +is posted it stays posted, i.e., is not reset when an exception occurs. +.PP +Few programs actually post exception handlers; most just post an error handler +with \fBonerror\fR. Such an error handler will be called by the IRAF Main +either when an exception occurs or when an error action is taken, i.e., +when a program is aborted for whatever reason. If a user exception handler +is not posted the default handler will be called, causing error restart of the +Main, calls to all \fBonerror\fR procedures, and transmission of the \fBerror\fR +statement to the CL. If the CL is interrupted while executing an external +program it passes the interrupt on to the child with \fBzintpr\fR and then +resumes normal processing. The external program retains control, and therefore +can choose to either ignore the interrupt or take some application dependent +action. + +.NH 2 +Memory Management +.PP +The IRAF system relies heavily on memory management for dynamic buffer +allocation in both system and applications software. +Both stack and heap storage are provided at the program interface level. +The \fBstack\fR is used primarily for "automatic" storage allocation, +i.e., for buffers which are allocated upon entry to a procedure and deallocated +upon exit from the procedure. Stack management incurs very little overhead +for small buffers. The \fBheap\fR is a more general storage mechanism; +buffers may be allocated and deallocated in any order, and allocation and +deallocation may occur in different procedures. A heap buffer may be +reallocated, i.e., changed in size. The stack is implemented portably in terms +of the heap, and hence need not concern us further here. +.sp 0.08i +.TS +center; +cb s +n l. +Memory Management Primitives +.sp +zmaloc \&(buffer, nbytes, status) allocate a buffer +zmfree \&(buffer, status) deallocate a buffer +zraloc \&(buffer, nbytes, status) reallocate a buffer +zlocva \&(variable, address) get address of a variable +zawset \&(bestsize, newsize, oldsize, textsize) adjust working set size +.TE +.sp 0.08i +.PP +Buffer space is allocated on the heap by the primitive \fBzmaloc\fR. +The address of a buffer at least \fInbytes\fR in size is returned +as the argument \fIbuffer\fR. Nothing is assumed about the alignment of +the buffer. The contents of the buffer are not assumed to be initialized. +.PP +The buffer address returned by \fBzmaloc\fR is in units of SPP \fBchars\fR +rather than in physical units. The \fBzlocva\fR primitive returns the +address of a \fBcsilrd\fR variable, array, or array element in the same +units. By using char address units and by doing all pointer dereferencing +by subscripting Fortran arrays, we avoid building knowledge of the memory +addressing characteristics of the host system into SPP programs. +The zero point of a char address is undefined; negative addresses are +possible depending on the implementation. It must be possible to store an +address in an integer variable, and it must be possible to perform +\fIsigned integer\fR comparisons and arithmetic on addresses. +.PP +A buffer allocated with \fBzmaloc\fR may be reallocated with \fBzraloc\fR. +In this case the \fIbuffer\fR argument is used both for input and for output. +If the input value is NULL a new buffer should be allocated, otherwise the +size of the buffer should be either increased or decreased depending on the +value of \fInbytes\fR. The buffer may be moved if necessary, provided the +contents of the buffer are preserved. This primitive may be implemented as +a call to \fBzmaloc\fR followed by an array copy and a call to \fBzmfree\fR +if desired, saving one kernel primitive, with significant loss of efficiency +in some applications. +.PP +A buffer allocated with \fBzmaloc\fR is deallocated with \fBzmfree\fR. +Deallocation need not involve physically returning memory pages to the +operating system; if the buffer is small this will not be possible. The buffer +being deallocated need not be at the end of the process address space. +.PP +The \fBzlocva\fR primitive returns the address in char units of the first +argument as the integer value of the second argument. Since Fortran is +call by reference, this is a simple matter of copying the pointer to the +first argument (as opposed to the value pointed to) to the integer location +pointed to by the second argument. +Only arguments of datatypes \fBcsilrd\fR are permitted in calls to \fBzlocva\fR. +Arguments of Fortran type COMPLEX, CHARACTER, and EXTERNAL are sometimes passed +using more than one physical argument (depending on the host compiler) +and hence cannot be used in procedures that operate upon an arbitrary datatype. +.PP +The \fBzawset\fR primitive is used both to determine and to change the amount +of physical memory in machine bytes available to a process, i.e., the +\fBworking set size\fR on a virtual memory machine. +If called with a \fIbestsize\fR of zero the current working set size and text +segment size is returned. If called with nonzero \fIbestsize\fR on a +virtual memory machine the working set size will be adjusted up or down +as indicated, returning the actual new working set size in \fInewsize\fR +and the old working set size in \fIoldsize\fR. +It is not an error if the amount of memory requested cannot be allocated; +the high level code will ask for what it wants but take what it gets. +High level routines which need lots of memory rely on this primitive to +avoid running out of memory on nonvirtual machines and to avoid thrashing +on virtual machines. +.PP +The high level code (\fBmalloc\fR) converts the address returned +by the kernel primitives into an integer valued offset (array index) into +the \fBMem\fR common. +The dynamically allocated buffer (which has nothing to do with the Mem common) +is referenced by indexing off the end of an \fBMem\fR array. +The portable MEMIO code ensures alignment between the physical buffer and +the "pointer" returned to the user (index into Mem). This technique should +work on any machine which permits referencing off the end of an array. +.PP +There is one place in the system code, however, which does something trickier +and which should be checked on a new system. +The \fBstropen\fR routine in FIO uses the +same pointer technique (for efficiency reasons) to reference a \fIstatically\fR +allocated \fBchar\fR array in the user program. If the compiler does not +guarantee that a statically allocated \fBchar\fR array will be aligned with +the array \fBMemc\fR in the Mem common this will not work, +and \fBstropen\fR will have to be modified. +.NH 2 +Procedure Call by Reference +.PP +Fortran allows an external procedure to be passed by reference to a +subprogram via the subprogram argument list. An external procedure passed +as an argument to a subprogram may be called by the subprogram but may not be +saved and called at some later time. IRAF (e.g. FIO and IMIO) requires the +capability to save a reference to a procedure in an integer variable for +execution at some later time. +.PP +The \fBzlocpr\fR primitive is used to determine the entry point address +(EPA) of an external procedure. IRAF assumes that the EPA of a procedure +may be stored in an integer variable and that two procedures with the same +EPA are identically the same procedure. No other operations are permitted +on EPA values, e.g., signed comparisons and arithmetic are not permitted. +.sp 0.08i +.TS +center; +cb +n. +Call by Reference Primitives +.sp +zlocpr \&(proc, entry_point_address) get address of a procedure +zcall1 \&(procedure, arg1) +zcall2 \&(procedure, arg1, arg2) +zcall3 \&(procedure, arg1, arg2, arg3) +zcall4 \&(procedure, arg1, arg2, arg3, arg4) +zcall5 \&(procedure, arg1, arg2, arg3, arg4, arg5) +.TE +.sp 0.08i +.PP +A \fBzcall\fIn\fR primitive is used to call an external subroutine +referenced by the integer variable \fIprocedure\fR, the entry point address +of the procedure returned in a prior call to \fBzlocpr\fR. +Only subroutines may be called by reference; there is no comparable facility +for functions. The datatypes of the arguments are unspecified but are +restricted to the SPP datatypes \fBcsilrd\fR. + +.NH 2 +Date and Time +.PP +Kernel primitives are required to read the system clock, to determine the +amount of cpu time consumed by a process (for performance measurements), +and to generate time delays. +.sp 0.08i +.TS +center; +cb s +n l. +Date and Time Primitives +.sp +zgtime \&(local_time, cpu_time) get clock time and cpu time +ztslee \&(delay) countdown timer +.TE +.sp 0.08i +.PP +The \fBzgtime\fR primitive returns two long integer arguments. The local +standard time in integer seconds since midnight on January 1, 1980 is +returned as the first argument (the "clock" time). The second argument +is the total cpu time consumed by the process since process execution, +in units of milliseconds. The countdown timer primitive \fBztslee\fR +suspends execution of the calling process for the specified number of +integer seconds. There is currently no provision for generating delays +of less than one second. + +.NH 2 +Sending a Command to the Host OS +.PP +The ability to send an explicitly machine dependent command to the host +system command interpreter is required by the CL and by some of the system +utilities. Any program which uses this command is bypassing the system +interface and is system dependent. Nonetheless it is very useful for the +\fIuser\fR to be able to send a command to the host without leaving the +IRAF environment, and certain of the system utilities are much easier to +code given the capability (e.g., \fBdiskspace\fR and \fBspy\fR). These +utilities help provide a consistent user interface on all systems, and in +many cases such a utility program can be built in a few minutes for a new +system. No science program or essential system utility bypasses the system +interface in this fashion. +.sp 0.08i +.TS +center; +cb s +n l. +Host OS Command Primitive +.sp +zoscmd \&(cmd, stdout, stderr, status) send a command to the host OS +.TE +.sp 0.08i +.PP +The command \fIcmd\fR may be any packed string acceptable to the host +system. The call does not return until the command has been executed. +The status OK or ERR is returned indicating success or failure. +If either of the filename strings \fIstdout\fR or \fIstderr\fR is nonnull +the associated output stream of the command will be directed (if possible) +to the named text file. + +.NH +Bit and Byte Primitives +.PP +The bit and byte primitives are not considered true kernel procedures since +they are purely numerical and are only potentially machine dependent. +These primitives are more properly part of the \fBprogram interface\fR than +the kernel, since they are callable from ordinary applications programs. +Both SPP or Fortran and C versions of most of the routines are supplied +with the system which will port to most modern minicomputers and some large +computers. The source directory is \fBsys$osb\fR. +The following classes of routines are required: +.sp 0.05i +.DS +\(bu bitwise boolean operations (and, or, etc.) +\(bu bitfield insertion and extraction +\(bu byte primitives (copy, swap, string pack/unpack) +\(bu type conversion for byte, unsigned short datatypes +\(bu machine independent integer format conversions +.DE +.sp 0.05i +.PP +The IRAF system uses 8 standard datatypes in compiled SPP programs, +as shown in the table below. Variables and arrays may be declared and +accessed conventionally using any of these datatypes. Data may additionally +be stored on disk, in images, and in memory in packed char or integer arrays +in the exotic datatypes \fBunsigned byte\fR, \fBunsigned short\fR, +and \fBpacked string\fR. +.sp 0.08i +.TS +center box; +cb s s +ci | ci | ci +lb | c | l. +Standard SPP Datatypes +_ +name suffix Fortran equivalent += +bool b LOGICAL +char c nonstandard +short s nonstandard +int i INTEGER +long l nonstandard +real r REAL +double d DOUBLE PRECISION +complex x COMPLEX +.TE +.sp 0.08i +.PP +The char and short datatypes are commonly implemented as INTEGER*2, +and long as INTEGER*4, but all could be implemented as the standard +INTEGER if necessary. To save space char may be implemented using a +signed byte datatype if the host Fortran compiler provides one, +provided the special datatype chosen may be equivalenced with the +standard datatypes (the minimum precision of a char is 8 bits signed). +IRAF assumes that the 7 types \fBcsilrdx\fR may be equivalenced in common +(e.g. in \fBMem\fR). The standard type suffixes \fBbcsilrdx\fR are +commonly appended to procedure names to identify the datatype or datatypes +upon which the procedure operates. +.NH 2 +Bitwise Boolean Primitives +.PP +The bitwise boolean primitives are used to set and clear bits or bitfields +in integer variables. The practice is portable provided the minimum +precision of an integer variable (16 bits) is not exceeded. Primitives +are provided for the 3 integer datatypes, i.e., short, int, and long, +denoted by the suffix \fB[sl]\fR in the table below. In other words, +the notation \fBand[sl]\fR refers to the procedures \fBands\fR and \fBandl\fR. +These quasi-primitives, unlike the true kernel primitives, are user callable +and are implemented as \fIfunctions\fR. +.sp 0.08i +.TS +center; +cb s +n n. +Bitwise Boolean Primitives +.sp +and,and[sl] \&(a, b) int = and \&(a, b) +or,or[sl] \&(a, b) int = or \&(a, b) +xor,xor[sl] \&(a, b) int = xor \&(a, b) +not,not[sl] \&(a, b) int = not \&(a, b) +.TE +.sp 0.08i +.PP +Bitwise boolean primitives are provided in many Fortran compilers +as integer intrinsic functions. If this is the case it suffices (and +is more efficient) to place a \fBdefine\fR statement in \fBiraf.h\fR to +map the IRAF name for the function to that recognized by the host Fortran +compiler. For example, +.DS +\fBdefine\fR and iand +.DE +would cause all occurrences of the identifier \fBand\fR in SPP programs to +be replaced by \fBiand\fR in the Fortran output, which the host compiler +would hopefully compile using inline code. +.NH 2 +Bitfield Primitives +.PP +A \fBbitfield\fR is an unsigned integer segment of a bit array, where the +number of bits in the segment must be less than or equal to NBITS_INT, +the number of bits in an integer. A \fBbit array\fR is a sequence of +bits stored one bit per bit in a char or integer array. The essential +thing about a bit array is that byte and word boundaries are irrelevant, +i.e., a bitfield may straddle a word boundary. +.sp 0.08i +.TS +center; +cb s +n n. +Bitfield Primitives +.sp +bitpak \&(intval, bit_array, bit_offset, nbits) integer \(-> bitfield +int = bitupk \&(bit_array, bit_offset, nbits) bitfield \(-> integer +.TE +.sp 0.08i +.PP +Bit offsets range from 1, not 0, to MAX_INT. A bitfield is zero-extended +when unpacked by \fBbitupk\fR, and unset bits are zeroed when an integer +is packed into a bitfield by \fBbitpak\fR. If the integer is too large +to fit in the bitfield it is truncated. These primitives should be +implemented in assembler on a machine like the VAX which has bitfield +instructions. +.NH 2 +Byte Primitives +.PP +The byte primitives are difficult to use portably in high level code +without building knowledge of the sizes of the SPP datatypes in bytes +into the code. Fortunately the byte primitives are rarely used; the most +common usage is in programs used to transport data between machines (e.g., +a magtape reader program). A number of machine constants are defined +in \fBiraf.h\fR to allow parameterization of programs which operate on +data in units of bytes. +.sp 0.08i +.TS +center box; +cb s +ci | ci +l | l. +Machine Parameters for Byte Data +_ +name definition += +SZB_CHAR machine bytes per char +NBITS_BYTE nbits in a machine byte +SZ_\fItype\fR size of datatype \fItype\fR (upper case) in chars +.TE +.sp 0.08i +.PP +On most machines the byte primitives can be written in Fortran by +representing a byte array as an array of CHARACTER*1. This suffices for +programs which merely move bytes around, but not for programs which +do numerical comparisons and arithmetic operations upon character data +using CHAR and ICHAR, because the collating sequence for CHARACTER data +in Fortran is not necessarily ASCII. +.PP +Nonetheless CHAR and ICHAR can be used on most machines to operate upon bytes, +i.e., upon non-CHARACTER data stored in CHARACTER*1 arrays. +Of course we are asking for trouble using CHARACTER for non-CHARACTER +operations, so routines which do so are potentially machine dependent. +Both Fortran and C versions of most of the byte primitives are supplied. +The \fBbytmov\fR primitive should be written in assembler on a machine such +as the VAX which can perform the operation in a single instruction +(it is even more important to perform this optimization for the \fBamov\fR +vector operators, which are more widely used). +.sp 0.08i +.TS +center; +cb s +n l. +Byte Primitives +.sp +bytmov \&(a, aoff, b, boff, nbytes) move an array of bytes +bswap2 \&(a, b, nbytes) swap every pair of bytes +bswap4 \&(a, b, nbytes) swap every 4 bytes +chrpak \&(a, aoff, b, boff, nchars) pack chars into bytes +chrupk \&(a, aoff, b, boff, nchars) unpack bytes into chars +strpak \&(a, b, maxchars) SPP string \(-> byte-packed string +strupk \&(a, b, maxchars) byte-packed string \(-> SPP string +.TE +.sp 0.08i +.PP +The \fBbytmov\fR primitive moves a portion of a byte array into a portion +of another byte array. The move is nondestructive, i.e., if the input +and output arrays overlap data must not be destroyed. The \fBzlocva\fR +primitive may be used to determine if the arrays will overlap. +Byte swapping is performed by the \fBbswap2\fR and \fBbswap4\fR primitives, +which swap every 2 bytes or every 4 bytes, respectively, regardless of the +number of bytes per short or long integer on the host machine. These routines +are used primarily to swap bytes in interchange data before is it unpacked into +host integers (or after packing into interchange format), hence the primitives +are defined independently of the host word size. A 2 byte swap interchanges +successive pairs of bytes; a 4 byte swap of two 4 byte integers rearranges +the bytes as 12345678 \(-> 43218765. +.PP +The \fBchrpak\fR and \fBchrupk\fR primitives pack and unpack SPP chars +into bytes, performing sign extension in the unpacking operation. +The mapping is nondestructive, i.e., the input and output arrays may +be the same, and the numeric value of a character is not changed +by the mapping (the collating sequence is not changed by the mapping). +If SZB_CHAR is 1, \fBchrpak\fR and \fBchrupk\fR are equivalent, and if +the input and output arrays are the same or do not overlap they are +equivalent to \fBbytmov\fR. +.PP +The \fBstrpak\fR and \fBstrupk\fR primitives pack and unpack SPP strings +into packed strings. A packed string is a sequence of zero or more characters, +packed one character per byte, delimited by end-of-string (EOS). +While SPP strings are always ASCII the collating sequence of a packed string +is whatever is used for character data by the host machine. +The mapping is nondestructive in the sense that the input and output arrays +may be the same. Since the collating sequence may be changed in the mapping +and the mapping need not be one-to-one, information may be lost if an +arbitrary string is packed and later unpacked. +.PP +A packed string is not the same as a Fortran CHARACTER variable or constant. +Many Fortran compilers use two physical arguments to pass a Fortran CHARACTER +argument to a subprocedure, while a packed string is always passed by reference +like an ordinary integer array. There is no machine independent way to fake +a Fortran string in an argument list. Furthermore, packed strings are heavily +used in the kernel for machine dependent filenames, and these file names +typically contain characters not permitted by the restrictive Fortran standard +character set. The packed string format is equivalent to that expected by +the C language. +.NH 2 +Vector Primitives +.PP +Nearly all of the operators in the vector operators package +(VOPS, \fBsys$vops\fR) are machine independent. The exceptions are the +\fBacht\fR primitives used to change the datatype of a vector to or from +one of the special datatypes \fBunsigned byte\fR and \fBunsigned short\fR. +An \fBacht\fR operator is provided for every possible type conversion +in the set of datatypes \fBcsilrdx\fR plus unsigned byte (\fBB\fR) and +unsigned short (\fBU\fR), for a total of 9 squared or 81 operators in all. +The \fBbool\fR datatype is not supported by VOPS. +.PP +Two type suffixes are used to specify the type conversion performed by an +operator; for example, \fBachtir\fR will convert an integer array into a +real array. In the table below the underscore stands for the set of +datatypes \fBUBcsilrdx\fR, hence each the operators shown is actually +a generic operator consisting of 9 type specific operators. +Both C and Fortran sources are provided for all primitives, the C sources +being more efficient. The Fortran operators will work on many hosts +but are potentially machine dependent and should be checked. The C versions +are more efficient since Fortran does not support the unsigned datatypes +and a masking operation must be performed to undo sign extension when +converting from unsigned to signed. +.sp 0.08i +.TS +center; +cb s +n l. +Machine Dependent Vector Primitives +.sp +acht_b \&(a, b, npix) SPP datatype \(-> unsigned byte +acht_u \&(a, b, npix) SPP datatype \(-> unsigned short +achtb_ \&(a, b, npix) unsigned byte \(-> SPP datatype +achtu_ \&(a, b, npix) unsigned short \(-> SPP datatype +.TE +.sp 0.08i +.PP +Many of the conversions do not preserve precision, i.e., double to real +or integer to unsigned byte. The imaginary part of a complex number is +discarded when converting to some other datatype, and the imaginary part +is set to zero when converting a non-complex datatype to complex. +All type conversion operators allow the conversion to be performed in +place, i.e., the input and output arrays may be the same. +.NH 2 +MII Format Conversions +.PP +The Machine Independent Integer format (MII) is used to transport binary +integer data between computers. The format is independent of the transmission +medium, and hence might be used to transport data via magnetic tape, over a +local area network, or over a modem. The MII integer format is equivalent +to that defined by the FITS image interchange format. The MII primitives are +used in the IRAF FITS reader and writer programs and will probably be used +in the GKS (Graphical Kernel System) software to implement a machine +independent VDM (Virtual Device Metafile). +.PP +MII defines 3 integer datatypes, 8 bit unsigned integer, 16 bit twos-complement +signed integer, and 32 bit twos-complement signed integer. An integer array +in MII format may be thought of as a stream of 8-bit bytes. In each 2 and +4 byte integer successive bytes are written in order of decreasing significance. +The sign bit is in the first byte of each 2 or 4 byte integer. For example, +two 16 bit integers would be represented in MII format as the following +sequence of 4 bytes. +.sp 0.08i +.TS +center; +ci ci +l l. +byte significance +.sp +1 high byte of first integer, including sign bit +2 low byte of first integer +3 high byte of second integer, including sign bit +4 low byte of second integer +.TE +.PP +The order of the bits within a byte is (must be) standardized at the +hardware level, else we would not be able to transmit character data via +cardimage tapes and modems. Hence the sign bit is bit 200B (octal) of the +first byte of an MII integer, the most significant bit is bit 100B of the +first byte, and so on. +.sp 0.08i +.TS +center; +cb s +n l. +MII Primitives +.sp +miipak \&(spp, mii, nelems, spp_type, mii_type) SPP \(-> MII +miiupk \&(mii, spp, nelems, mii_type, spp_type) MII \(-> SPP +intlen = miilen \&(n_mii_elements, mii_type) get size of array +.TE +.sp 0.08i +.PP +SPP or Fortran integer data is converted to MII format with \fBmiipak\fR, +and MII data is converted to SPP format with \fBmiiupk\fR. +The argument \fIspp\fR refers to an SPP integer array of datatype +\fIspp_type\fR, and \fImii\fR refers to an MII byte stream of MII datatype +\fImii_type\fR. The legal integer values of \fImii_type\fR are 8, 16, +and 32. MII data is stored in an SPP array of type \fBint\fR. +The length of the SPP integer array required to store \fIn_mii_elements\fR +of MII type \fImii_type\fR is returned by the primitive \fBmiilen\fR. +.PP +An SPP implementation of the MII primitives which should work for all +host machines with 16 or 32 bit twos-complement signed integer datatypes +is supplied with the system. Most modern minicomputers fall into this class. +All one need do to use the supplied MII primitives is determine whether or +not byte swapping is necessary. If the parameter BYTE_SWAP2 is defined +as YES in \fBiraf.h\fR then \fBbswap2\fR will be called to swap 2 byte +MII integers, producing an SPP \fBshort\fR as output. If the parameter +BYTE_SWAP4 is defined as YES then \fBbswap4\fR will be called to swap 4 +byte MII integers, producing an SPP \fBlong\fR as output. +.NH 2 +Machine Constants for Mathematical Libraries +.PP +The most often used machine constants are parameterized in include files +for use within SPP programs. Defined constants are the most readable +and efficient way to represent machine constants, but not the most accurate +for floating point quantities. Furthermore, SPP defined constants cannot +be used within Fortran procedures; functions returning the machine constants +are often used instead. The most widely used set of such functions appears +to be those developed at Bell Laboratories for the \fIPort\fR mathematical +subroutine library. +.sp 0.08i +.TS +center; +cb s +n l. +Mathematical Machine Constants +.sp +int = i1mach \&(parameter) get INTEGER machine constants +real = r1mach \&(parameter) get REAL machine constants +double = d1mach \&(parameter) get DOUBLE PRECISION machine constants +.TE +.sp 0.08i +.PP +These routines are used in many numerical packages, including the IEEE +signal processing routines and the NCAR graphics software. Documentation +is given in the Fortran sources; values of the constants have already been +prepared for most of the computers used at scientific centers. The integer +codes of the machine parameters are parameterized in \fBlib$mach.h\fR for +use in SPP programs. + +.NH +System Parameterization and Tuning +.PP +All machine dependent system and language parameters are defined in the two +SPP include files \fBlib$iraf.h\fR and \fBlib$config.h\fR, in the C include +file \fBcl$config.h\fR, and in the CL script file \fBlib$clpackage.cl\fR. +Additional machine dependent include files will often be used (\fIshould\fR +be used) to implement the kernel for a particular machine but these are too +host specific to be described here. +.PP +The include file \fBlib$iraf.h\fR is automatically loaded by the SPP whenever +an SPP source file is preprocessed, hence the defines in \fBiraf.h\fR are +effectively part of the SPP language. This global include file defines +the language parameters (e.g., EOF, ERR) as well as many machine constants. +The two configuration files \fBlib$config.h\fR and \fBcl$config.h\fR define +additional constants pertaining to the host OS as well as various size limiting +and heuristic parameters used to tune IRAF for optimum performance on a given +host system. The CL script file \fBlib$clpackage.cl\fR contains \fBset +environment\fR declarations for all system directories and default devices. +.PP +Documentation for the individual machine constants and system tuning parameters +is maintained directly in the source files to ensure that it is up to date. +Some of the parameters in \fBconfig.h\fR pertain only to the inner workings +of the program interface and changes can be expected in successive releases +of the IRAF system, without corresponding changes to the external +specifications of the program interface. + +.NH +Other Machine Dependencies +.PP +In the ideal world all of the machine dependence of the system would be +concentrated into the kernel and a few include files. While this has been +achieved for the scientific software some of the system utilities currently +bypass the system interface and are machine dependent. +This is not necessarily a bad thing; if a certain capability is only needed +by one or two system utilities the machine dependence of the system will often +be less if we take the simple way out and write a system dependent program +than if we further expand the formal system interface. +.PP +The machine dependent utilities are in the packages \fBsystem\fR and +\fBsoftools\fR. All machine dependent procedures are listed in the README +files in the package directories. The string MACHDEP is placed in the source +files to mark any machine dependent code segments. The machine dependent +utilities are not essential to the use of the system, but are useful for +maintaining the system. Examples of machine dependent software utilities +include \fBmake\fR, \fBxcompile\fR, and the generic preprocessor. These +utilities will all eventually be replaced by portable programs. +Machine dependent system utilities include \fBallocate\fR and \fBdeallocate\fR, +\fBedit\fR (the interface to the host editor), and \fBdiskspace\fR. +These utilities are actually just command level interfaces to the host +system command interpreter, and are easy to modify for a new host. +.NH 2 +Machine Dependencies in the CL +.PP +As noted earlier, from a structural design point of view the Command Language +is an IRAF applications program; as such it is highly portable. The CL is not +completely portable because it is written in C. Since the CL is written in +C it cannot reference the standard include files \fBiraf.h\fR and +\fBconfig.h\fR, and therefore has its own machine dependent \fBconfig.h\fR +include file instead. Since the CL requires file i/o, process control and +exception handling, formated i/o, the TTY interface, etc., it is interfaced +to the IRAF program interface (the CL has a special Main of its own but this +is portable). +.PP +The C code in the CL is purely numeric, like the Fortran in SPP based +applications programs. All communication with the host system is via the +IRAF program interface. The program interface is written in SPP, i.e., +Fortran, and Fortran procedures +cannot portably be called from C (see \(sc3.1.2). To render the bulk of the +code portable a special C callable subset of the program interface is +defined for the CL, and all CL calls to program interface routines are via +this interface. Efficiency is not a problem because the CL does little i/o; +the CL is the control center of the system, and tends to be compute bound when +it is not idle waiting for a command. +.PP +In summary, to interface the CL to a new system it is necessary to first edit +the \fBcl$config.h\fR, which parameterizes the characteristics of the host +system and which contains the system tuning parameters affecting the CL. +The CL interface to the subset program interface consists of a set of +rather simple interface procedures in the file \fBcl$machdep.h\fR. +If you are lucky these will not have to be changed to port the CL to your host +computer, but even if the routines have to be modified they are few in number +and quite simple in nature. +.NH +Specifications for the Kernel Procedures +.PP +The remainder of this document consists of a summary of the machine dependent +procedures, followed by the detailed technical specifications for each +procedure. Only the specifications for the kernel primitives are given; +the bitwise boolean primitives are part of the program interface and are +documented elsewhere. Most likely either the Fortran or C code supplied for +the bitwise primitives will be usable on a new host system without significant +modification. +.PP +While the kernel consists of quite a few procedures, this does not necessarily +mean that it is going to be harder to implement than if there were only a +quarter or a third as many procedures. Our design goal was to minimize the +complexity and size of the kernel, and we felt that it was more important to +define simple, single function primitives than to minimize the \fInumber\fR +of primitives. +.PP +A large part of the kernel consists of device driver +subroutines; on a system which provides device independent i/o these may map +to the same low level procedures and a count of the number of driver +subroutines will have little meaning. On a system which does not have device +independent i/o it will be easier to implement separate procedures for each +device than to try to make the host system look like it has device independent +i/o (FIO already does that anyhow). Furthermore, the provision for separate +drivers makes it easy to optimize i/o for a particular device, and makes it +possible to dynamically interface new devices to FIO without modifying the +basic system. +.PP +All kernel procedures are called by virtual operating system procedures +in the program interface. The kernel procedures are \fInot\fR callable +directly from applications code. Extensive error checking is performed by +the high level code before a kernel procedure is called, hence error +checking in kernel procedures is largely redundant and should be omitted +if it will significantly compromise performance. Do not get clever +with kernel procedures; \fIkeep it simple\fR. +.sp 0.08i +.TS +center box; +cb s s +ci | ci | ci +l | c | l. +Summary of Kernel Constants +_ +name value usage += +APPEND 4 write at EOF +BINARY_FILE 12 +BOF \(mi3 beginning of file +EOF \(mi2 end of file +EOS '\\\\0' end of string delimiter +ERR \(mi1 function was unsuccessful +FI_DIRECTORY 2 directory file (\fBzfinfo\fR) +FI_EXECUTABLE 3 executable file +FI_REGULAR 1 ordinary file +FI_SPECIAL 4 special file +FSTT_BLKSIZE 1 device block size +FSTT_FILSIZE 2 file size, bytes +FSTT_MAXBUFSIZE 4 maximum transfer size +FSTT_OPTBUFSIZE 3 optimum transfer size +LEN_JUMPBUF ?? integer length of \fBzsvjmp\fR buffer +NEW_FILE 5 create a new file +NO 0 no (false) +OK 0 function successfully completed +PR_CONNECTED 1 connected subprocess +PR_DETACHED 2 detached process +PR_HOST 3 process spawned by host +QUERY_PROTECTION 2 query file protection (\fBzfprot\fR) +READ_ONLY 1 file access modes +READ_WRITE 2 +REMOVE_PROTECTION 0 remove file protection (\fBzfprot\fR) +SET_PROTECTION 1 set file protection (\fBzfprot\fR) +SZ_LINE 161 default textfile line length +TEXT_FILE 11 file types +WRITE_ONLY 3 +X_ACV 501 access violation +X_ARITH 502 arithmetic error +X_INT 503 interrupt +X_IPC 504 write to IPC with no reader +YES 1 yes (true) +.TE +.sp 0.08i + + +.bp +.LG +.ce +\fBSummary of Machine Dependent Procedures\fR +.NL +.sp 2 +.TS +center; +cb s +n l. +Kernel Primitives +.sp +\fBzawset\fR \&(bestsize, newsize, oldsize, textsize) adjust working set size +\fBzcall\fIn\fR \&(procedure, arg1,...,arg\fIn\fR) call by reference +\fBzclcpr\fR \&(pid, exit_status) close connected subprocess +\fBzcldir\fR \&(chan, status) close directory +\fBzcldpr\fR \&(jobcode, killflag, exit_status) close or dequeue detached process +\fBzdojmp\fR \&(jumpbuf, status) restore process status +\fBzfacss\fR \&(osfn, mode, type, status) access file +\fBzfaloc\fR \&(osfn, nbytes, status) preallocate a binary file +\fBzfchdr\fR \&(new_directory, status) change directory +\fBzfdele\fR \&(osfn, status) delete a file +\fBzfgcwd\fR \&(osdir, maxch, status) get current working directory +\fBzfinfo\fR \&(osfn, out_struct, status) get info on a file +\fBzfmkcp\fR \&(old_osfn, new_osfn, status) make null copy of a file +\fBzfpath\fR \&(osfn, pathname, maxch, status) osfn to pathname +\fBzfprot\fR \&(osfn, prot_flag, status) file protection +\fBzfrnam\fR \&(old_osfn, new_osfn, status) rename a file +\fBzfsubd\fR \&(osdir, subdir, new_osdir, maxch, nchars) get subdirectory name +\fBzfxdir\fR \&(osfn, osdir, maxch, status) extract OS directory prefix +\fBzgfdir\fR \&(chan, osfn, maxch, status) get next OSFN from directory +\fBzgtime\fR \&(local_time, cpu_time) get clock time and cpu time +\fBzgtpid\fR \&(pid) get process id of current process +\fBzintpr\fR \&(pid, exception, status) interrupt connected subprocess +\fBzlocpr\fR \&(proc, entry_point_address) get EPA of a procedure +\fBzlocva\fR \&(variable, address) get address of a variable +\fBzmaloc\fR \&(buffer, nbytes, status) allocate a buffer +\fBzmfree\fR \&(buffer, status) deallocate a buffer +\fBzopcpr\fR \&(process, inchan, outchan, pid) open connected subprocess +\fBzopdir\fR \&(osfn, chan) open a directory +\fBzopdpr\fR \&(process, bkgfile, jobcode) open or queue detached process +\fBzoscmd\fR \&(cmd, stdout, stderr, status) send a command to the host JCL +\fBzpanic\fR \&(errcode, errmsg) panic exit +\fBzraloc\fR \&(buffer, nbytes, status) reallocate a buffer +\fBzsvjmp\fR \&(jumpbuf, status) save process status +\fBztslee\fR \&(delay_in_seconds) countdown timer +\fBzxgmes\fR \&(os_exception, errmsg, maxch) get exception code and message +\fBzxwhen\fR \&(exception, new_handler, old_handler) post an exception +.TE +.sp 2 +.TS +center box; +cb s s s s s s s s s +ci | ci | ci ci ci ci ci ci ci ci +l | c | c c c c c c c c. +Text File Device Drivers +_ +device code opn cls get put fls not sek stt += +normal (disk) tx * * * * * * * * +terminal ty * * * * * * +.TE +.sp 2 +.TS +center box; +cb s s s s s s s +ci | ci | ci ci ci ci ci ci +l | c | c c c c c c. +Binary File Device Drivers +_ +device code opn cls ard awr awt stt += +normal (disk) bf * * * * * * +line printer lp * * * * * +IPC pr * * * * +static file sf * * * * * * +magtape mt * * * * * * +.TE +.sp 2 +.TS +center; +cb s +n l. +Magtape Device Primitives +.sp +\fBzzopmt\fR \&(drive, density, mode, oldrec, oldfile, newfile, chan) open +\fBzzclmt\fR \&(chan, mode, nrecords, nfiles, status) close +\fBzzrdmt\fR \&(chan, buf, maxbytes) aread +\fBzzwrmt\fR \&(chan, buf, nbytes) awrite +\fBzzwtmt\fR \&(chan, nrecords, nfiles, status) await +\fBzzrwmt\fR \&(chan, status) arewind +.TE +.sp 2 +.TS +cb s +n l. +Bit and Byte Primitives +.sp +\fBand,and[sl]\fR \&(a, b) bitwise and +\fBor,or[sl]\fR \&(a, b) bitwise or +\fBxor,xor[sl]\fR \&(a, b) exclusive or +\fBnot,not[sl]\fR \&(a, b) complement +\fBbitpak\fR \&(intval, bit_array, bit_offset, nbits) integer \(-> bitfield +int = \fBbitupk\fR \&(bit_array, bit_offset, nbits) bitfield \(-> integer +\fBbytmov\fR \&(a, aoff, b, boff, nbytes) move an array of bytes +\fBbswap2\fR \&(a, b, nbytes) swap every pair of bytes +\fBbswap4\fR \&(a, b, nbytes) swap every 4 bytes +\fBchrpak\fR \&(a, aoff, b, boff, nchars) pack chars into bytes +\fBchrupk\fR \&(a, aoff, b, boff, nchars) unpack bytes into chars +\fBstrpak\fR \&(a, b, maxchars) SPP string \(-> byte-packed string +\fBstrupk\fR \&(a, b, maxchars) byte-packed string \(-> SPP string +\fBacht_b\fR \&(a, b, npix) SPP datatype \(-> unsigned byte +\fBacht_u\fR \&(a, b, npix) SPP datatype \(-> unsigned short +\fBachtb_\fR \&(a, b, npix) unsigned byte \(-> SPP datatype +\fBachtu_\fR \&(a, b, npix) unsigned short \(-> SPP datatype +\fBmiipak\fR \&(spp, mii, nelems, spp_type, mii_type) SPP \(-> MII +\fBmiiupk\fR \&(mii, spp, nelems, mii_type, spp_type) MII \(-> SPP +intlen = \fBmiilen\fR \&(n_mii_elements, mii_type) get length of MII array +int = \fBi1mach\fR \&(parameter) get int machine constants +real = \fBr1mach\fR \&(parameter) get real machine constants +double = \fBd1mach\fR \&(parameter) get double machine constants +.TE diff --git a/unix/os/doc/ostoc.ms b/unix/os/doc/ostoc.ms new file mode 100644 index 00000000..686039fd --- /dev/null +++ b/unix/os/doc/ostoc.ms @@ -0,0 +1,130 @@ +.RP +.ND +.TL +Contents +.PP +Hi there. +.pn 0 +.bp +.ce +\fBContents\fR +.sp 3 +1.\h'|0.4i'\fBIntroduction\fP\l'|5.6i.'\0\01 +.sp +2.\h'|0.4i'\fBStructure of the IRAF System Software\fP\l'|5.6i.'\0\02 +.sp +3.\h'|0.4i'\fBThe IRAF System Interface\fP\l'|5.6i.'\0\04 +.br +\h'|0.4i'3.1.\h'|0.9i'The Language Interface\l'|5.6i.'\0\04 +.br +\h'|0.9i'3.1.1.\h'|1.5i'Fortran\l'|5.6i.'\0\05 +.br +\h'|0.9i'3.1.2.\h'|1.5i'Mixing C and Fortran in the same System\l'|5.6i.'\0\06 +.br +\h'|0.9i'3.1.3.\h'|1.5i'Critique of C as a Scientific Language\l'|5.6i.'\0\08 +.br +\h'|0.9i'3.1.4.\h'|1.5i'The IRAF Subset Preprocessor Language\l'|5.6i.'\0\09 +.br +\h'|0.9i'3.1.5.\h'|1.5i'Limitations of the Subset Preprocessor\l'|5.6i.'\0\010 +.br +\h'|0.4i'3.2.\h'|0.9i'Bootstrapping the System\l'|5.6i.'\0\011 +.br +\h'|0.4i'3.3.\h'|0.9i'The IRAF Kernel\l'|5.6i.'\0\011 +.br +\h'|0.4i'3.4.\h'|0.9i'The Virtual Machine Model\l'|5.6i.'\0\012 +.br +\h'|0.9i'3.4.1.\h'|1.5i'The Minimal Host Machine\l'|5.6i.'\0\012 +.br +\h'|0.9i'3.4.2.\h'|1.5i'The Ideal Host Machine\l'|5.6i.'\0\013 +.sp +4.\h'|0.4i'\fBA Reference Manual for the IRAF Kernel\fP\l'|5.6i.'\0\015 +.br +\h'|0.4i'4.1.\h'|0.9i'Conventions\l'|5.6i.'\0\016 +.br +\h'|0.4i'4.2.\h'|0.9i'Avoiding Library Conflicts\l'|5.6i.'\0\017 +.br +\h'|0.4i'4.3.\h'|0.9i'File I/O\l'|5.6i.'\0\017 +.br +\h'|0.9i'4.3.1.\h'|1.5i'Text Files\l'|5.6i.'\0\018 +.br +\h'|0.9i'4.3.2.\h'|1.5i'Binary Files\l'|5.6i.'\0\020 +.br +\h'|0.9i'4.3.3.\h'|1.5i'Specifying Device Parameters\l'|5.6i.'\0\021 +.br +\h'|0.9i'4.3.4.\h'|1.5i'Standard File Devices\l'|5.6i.'\0\023 +.br +\h'|1.5i'4.3.4.1.\h'|2.2i'The User Terminal\l'|5.6i.'\0\023 +.br +\h'|1.5i'4.3.4.2.\h'|2.2i'The Line Printer Device\l'|5.6i.'\0\024 +.br +\h'|1.5i'4.3.4.3.\h'|2.2i'Interprocess Communication\l'|5.6i.'\0\025 +.br +\h'|1.5i'4.3.4.4.\h'|2.2i'Imagefile Access\l'|5.6i.'\0\026 +.br +\h'|1.5i'4.3.4.5.\h'|2.2i'Magtape Devices\l'|5.6i.'\0\028 +.br +\h'|0.4i'4.4.\h'|0.9i'Filename Mapping\l'|5.6i.'\0\031 +.br +\h'|0.9i'4.4.1.\h'|1.5i'Virtual Filenames\l'|5.6i.'\0\032 +.br +\h'|1.5i'4.4.1.1.\h'|2.2i'Logical Directories and Pathnames\l'|5.6i.'\0\032 +.br +\h'|1.5i'4.4.1.2.\h'|2.2i'Filename Extensions\l'|5.6i.'\0\033 +.br +\h'|0.9i'4.4.2.\h'|1.5i'Filename Mapping Algorithm\l'|5.6i.'\0\034 +.br +\h'|0.4i'4.5.\h'|0.9i'Directory Access\l'|5.6i.'\0\037 +.br +\h'|0.4i'4.6.\h'|0.9i'File Management Primitives\l'|5.6i.'\0\038 +.br +\h'|0.4i'4.7.\h'|0.9i'Process Control\l'|5.6i.'\0\039 +.br +\h'|0.9i'4.7.1.\h'|1.5i'Overview and Terminology\l'|5.6i.'\0\039 +.br +\h'|0.9i'4.7.2.\h'|1.5i'Synchronous Subprocesses\l'|5.6i.'\0\040 +.br +\h'|0.9i'4.7.3.\h'|1.5i'Standard IPC Commands\l'|5.6i.'\0\043 +.br +\h'|0.9i'4.7.4.\h'|1.5i'Example\l'|5.6i.'\0\045 +.br +\h'|0.9i'4.7.5.\h'|1.5i'Background Jobs\l'|5.6i.'\0\046 +.br +\h'|0.9i'4.7.6.\h'|1.5i'The Process and IRAF Mains\l'|5.6i.'\0\048 +.br +\h'|1.5i'4.7.6.1.\h'|2.2i'The Process Main\l'|5.6i.'\0\048 +.br +\h'|1.5i'4.7.6.2.\h'|2.2i'The IRAF Main\l'|5.6i.'\0\050 +.br +\h'|0.9i'4.7.7.\h'|1.5i'Process Control Primitives\l'|5.6i.'\0\051 +.br +\h'|0.4i'4.8.\h'|0.9i'Exception Handling\l'|5.6i.'\0\052 +.br +\h'|0.4i'4.9.\h'|0.9i'Memory Management\l'|5.6i.'\0\054 +.br +\h'|0.4i'4.10.\h'|0.9i'Procedure Call by Reference\l'|5.6i.'\0\055 +.br +\h'|0.4i'4.11.\h'|0.9i'Date and Time\l'|5.6i.'\0\056 +.br +\h'|0.4i'4.12.\h'|0.9i'Sending a Command to the Host OS\l'|5.6i.'\0\056 +.sp +5.\h'|0.4i'\fBBit and Byte Primitives\fP\l'|5.6i.'\0\057 +.br +\h'|0.4i'5.1.\h'|0.9i'Bitwise Boolean Primitives\l'|5.6i.'\0\057 +.br +\h'|0.4i'5.2.\h'|0.9i'Bitfield Primitives\l'|5.6i.'\0\058 +.br +\h'|0.4i'5.3.\h'|0.9i'Byte Primitives\l'|5.6i.'\0\058 +.br +\h'|0.4i'5.4.\h'|0.9i'Vector Primitives\l'|5.6i.'\0\060 +.br +\h'|0.4i'5.5.\h'|0.9i'MII Format Conversions\l'|5.6i.'\0\060 +.br +\h'|0.4i'5.6.\h'|0.9i'Machine Constants for Mathematical Libraries\l'|5.6i.'\0\061 +.sp +6.\h'|0.4i'\fBSystem Parameterization and Tuning\fP\l'|5.6i.'\0\062 +.sp +7.\h'|0.4i'\fBOther Machine Dependencies\fP\l'|5.6i.'\0\062 +.br +\h'|0.4i'7.1.\h'|0.9i'Machine Dependencies in the CL\l'|5.6i.'\0\063 +.sp +8.\h'|0.4i'\fBSpecifications for the Kernel Procedures\fP\l'|5.6i.'\0\063 diff --git a/unix/os/doc/zalocd.hlp b/unix/os/doc/zalocd.hlp new file mode 100644 index 00000000..3b98944c --- /dev/null +++ b/unix/os/doc/zalocd.hlp @@ -0,0 +1,53 @@ +.help zalocd Aug85 "System Interface" +.ih +NAME +zalocd -- set, remove, or query device allocation +.ih +SYNOPSIS +.nf +zalocd (device, action, status) + +packed char device[ARB] # device 1 +int action # operation to be performed +int status +.fi +.ih +DESCRIPTION +The named logical device is either allocated or deallocated, or the device +allocation status is queried, depending upon the value of the \fIaction\fR +argument. + +.nf + DEALLOCATE_DEVICE 0 + ALLOCATE_DEVICE 1 + QUERY_ALLOCATION 2 +.fi + +By allocating a device we mean that [1] the device is reserved for use by the +owner of the process issuing the request, and [2] the device is readied for +opening by the process issuing the request, or by a subprocess of the process +issuing the request. If the device is a tape drive the drive should be +mounted foreign (unlabeled) and the density should be set if so indicated +in the + +It is not an error to attempt to allocate a device which is already allocated, +nor is it an error to attempt to deallocate a device which is not currently +allocated. +.ih +RETURN VALUE +OK is returned if a set_protection or remove_protection operation is +successful. YES (protected) or NO (not protected) is returned in response +to a query. ERR is returned if the named file does not exist or if +the operation cannot be performed. +.ih +NOTES +FIO will query for file protection before attempting to delete a file. +If the host system does not provide file protection facilities they can +often be faked by creating a hidden file in the same directory; the existence +of the file will indicate that the file is protected. If the hidden file +technique is used, the hidden file should not be seen when the directory +is read by the high level code. +.ih +SEE ALSO +zfdele +.endhelp diff --git a/unix/os/doc/zardbf.hlp b/unix/os/doc/zardbf.hlp new file mode 100644 index 00000000..086d17bb --- /dev/null +++ b/unix/os/doc/zardbf.hlp @@ -0,0 +1,56 @@ +.help zardbf May84 "System Interface" +.ih +NAME +zardbf -- asynchronous read from a binary file +.ih +SYNOPSIS +.nf +zardbf (chan, buf, maxbytes, loffset) + +int chan # OS channel assigned to file +char buf[maxbytes] # output buffer +int maxbytes # maximum number of bytes to read +long loffset # file offset of first byte +.fi +.ih +DESCRIPTION +Initiate a read of at most \fImaxbytes\fR bytes from channel \fIchan\fR into +the buffer \fIbuf\fR. If the file associated with \fIchan\fR is a blocked file +the transfer begins at the one-indexed file offset \fIloffset\fR, specified +in units of bytes. The file offset must be greater than or equal to 1 and +less than or equal to the size of the file in bytes plus one. If the file is +a streaming file the file offset argument is ignored. If the file is blocked +\fIloffset\fR must be an integral multiple of the device block size, +i.e., the transfer must be aligned on a device block boundary. +At most \fImaxbytes\fR bytes are read. If the physical file block is +larger than \fImaxbytes\fR bytes the additional data is discarded. + +A read from a streaming file returns the next physical block in the file. +Successive blocks may vary in size; the size of a block is fixed when the +block is written (appended) to the file by \fBzawrbf\fR. If \fBzawrbf\fR +writes a block of length N bytes, the corresponding call to \fBzardbf\fR will +return either N bytes or \fImaxbytes\fR bytes, whichever is smaller, +discarding any additional data if \fImaxbytes\fR is less than N. +.ih +RETURN VALUE +The wait primitive \fBzawtbf\fR must be called after every asynchronous read +to get the transfer status. ERR is returned if a read error occurs or if the +channel number or file offset is illegal. If the read operation is successful +the actual number of bytes read is returned; zero is returned for a read at EOF. +.ih +NOTES +The transfer is NOT guaranteed to be asynchronous and the calling program +must not assume that \fBzardbf\fR will return immediately. +The \fBzawtbf\fR primitive must be called and the status checked before +another i/o request is issued to the channel. Only a single request may +be pending on a channel at a time. + +This primitive is called by the FIO routine \fBaread\fR which verifies that +the transfer is aligned and in-bounds, that a transfer is not already in +progress, and so on before calling \fBzardbf\fR. +A request to read zero bytes will not be passed to \fBzardbf\fR +and should be considered an error to avoid confusion with a read at EOF. +.ih +SEE ALSO +zawtbf, zawrbf, zfiobf +.endhelp diff --git a/unix/os/doc/zawrbf.hlp b/unix/os/doc/zawrbf.hlp new file mode 100644 index 00000000..8ff1b017 --- /dev/null +++ b/unix/os/doc/zawrbf.hlp @@ -0,0 +1,56 @@ +.help zawrbf May84 "System Interface" +.ih +NAME +zawrbf -- asynchronous write to a binary file +.ih +SYNOPSIS +.nf +zawrbf (chan, buf, nbytes, loffset) + +int chan # OS channel assigned to file +char buf[nbytes] # buffer to be copied to file +int nbytes # number of bytes to be written +long loffset # file offset of first byte +.fi +.ih +DESCRIPTION +Initiate a write of exactly \fInbytes\fR bytes from the buffer \fIbuf\fR +to the channel \fIchan\fR. If the file associated with \fIchan\fR is a +blocked file the transfer begins at the one-indexed file offset \fIloffset\fR, +specified in units of bytes. The file offset must be greater than or equal +to 1 and less than or equal to the size of the file in bytes plus one. +If the file is a streaming file the file offset argument is ignored. +If the file is blocked \fIloffset\fR must be an integral multiple of the +device block size, i.e., the transfer must be aligned on a device block +boundary. A request to write zero bytes is ignored. + +If writing entirely within the interior of the file \fInbytes\fR must be an +integral multiple of the device block size. If writing at EOF any number of +bytes may be written (provided the maximum transfer size is not exceedd). +A file may be extended by writing at EOF or by overwriting EOF in a large +transfer. If the last block in the file is a partial block the file must +be extended by reading the partial block into memory, appending the new data, +and then overwriting EOF with the larger block. File offsets must be explicit +byte offsets, i.e., the constants BOF and EOF are not recognized for binary +file offsets. +.ih +RETURN VALUE +The wait primitive \fBzawtbf\fR must be called after every asynchronous write +to get the transfer status. ERR is returned if a write error occurs or if the +channel number or file offset is illegal. If the write operation is successful +the actual number of bytes written is returned. +.ih +NOTES +The transfer is NOT guaranteed to be asynchronous and the calling program +must not assume that \fBzawrbf\fR will return immediately. +The \fBzawtbf\fR primitive must be called and the status checked before +another i/o request is issued to the channel. Only a single request may +be pending on a channel at a time. + +This primitive is called by the FIO routine \fBawrite\fR which verifies that +the transfer is aligned and in-bounds, that a transfer is not already in +progress, and so on before calling \fBzawrbf\fR. +.ih +SEE ALSO +zawtbf, zardbf, zfiobf +.endhelp diff --git a/unix/os/doc/zawset.hlp b/unix/os/doc/zawset.hlp new file mode 100644 index 00000000..7d85bc5e --- /dev/null +++ b/unix/os/doc/zawset.hlp @@ -0,0 +1,42 @@ +.help zawset May84 "System Interface" +.ih +NAME +zawset -- adjust working set size +.ih +SYNOPSIS +.nf +zawset (requested_size, newsize, oldsize, textsize) + +int requested_size # desired working set size, bytes +int newsize # working set allocated, bytes +int oldsize # old working set size, bytes +int textsize # size of text segment +.fi +.ih +DESCRIPTION +Adjust the amount of physical memory allocated to a process, i.e., the +working set size on a virtual memory machine. The amount of additional +data space that can be allocated and used by a process without thrashing +on a virtual memory machine is \fInewsize\fR bytes minus some fraction +of the text segment size (executable instructions) and minus the data space +already in use. + +The actual working set size returned in \fInewsize\fR need not be what was +requested. The old working set size \fIoldsize\fR may be used to reset the +working set size of the process to its original value when the space is no +longer needed. If \fIrequested_size\fR is negative or zero the current size is +returned in both output arguments and the working set size is not changed. +On a nonvirtual memory machine the "working set size" is a machine constant +fixed by the addressing range of the hardware, hence the requested size is +ignored. +.ih +RETURN VALUE +Valid \fInewsize\fR, \fIoldsize\fR and \fBtextsize\fR are always returned. +.ih +NOTES +It is up to the high level code to supply the necessary heuristics to avoid +thrashing on a virtual memory machine. +.ih +SEE ALSO +zmalloc, zmfree, zraloc +.endhelp diff --git a/unix/os/doc/zawtbf.hlp b/unix/os/doc/zawtbf.hlp new file mode 100644 index 00000000..cf387772 --- /dev/null +++ b/unix/os/doc/zawtbf.hlp @@ -0,0 +1,34 @@ +.help zawtbf May84 "System Interface" +.ih +NAME +zawtbf -- wait for an asynchronous i/o transfer to complete +.ih +SYNOPSIS +.nf +zawtbf (chan, status) + +int chan # OS channel assigned to file +int status # number of bytes read or written +.fi +.ih +DESCRIPTION +If a transfer is in progress on the channel \fIchan\fR, process execution +is suspended until the transfer completes. If the channel is inactive +control returns immediately. +.ih +RETURN VALUE +ERR is returned if an i/o error occurred during the last transfer. +If the transfer was successful the number of bytes read or written is +returned. A read at EOF returns a status value of zero. +Repeated calls to \fBzawtbf\fR following a single i/o request continue +to return the same value. +.ih +NOTES +FIO guarantees that \fBzawtbf\fR will be called after every asynchronous +i/o transfer and that only a single i/o request will be posted to a channel +at a time. If an i/o error occurs on the channel it should be cleared by +the next request, i.e., errors should not "stick". +.ih +SEE ALSO +zardbf, zawrbf, zfiobf +.endhelp diff --git a/unix/os/doc/zcall.hlp b/unix/os/doc/zcall.hlp new file mode 100644 index 00000000..ae7af4ff --- /dev/null +++ b/unix/os/doc/zcall.hlp @@ -0,0 +1,39 @@ +.help zcall,zcall1,zcall2,zcall3,zcall4,zcall5 May84 "System Interface" +.ih +NAME +zcall -- call an external procedure by reference +.ih +SYNOPSIS +.nf +zcall1 (procedure, arg1) +zcall2 (procedure, arg1, arg2) +zcall3 (procedure, arg1, arg2, arg3) +zcall4 (procedure, arg1, arg2, arg3, arg4) +zcall5 (procedure, arg1, arg2, arg3, arg4, arg5) + +int procedure # reference to external procedure +arb arg1, ..., arg\fIn\fR # arguments for external procedure +.fi +.ih +DESCRIPTION +The subroutine referenced by the magic integer passed as the first argument +is called as a subprocedure. The \fIn\fR arguments to \fBzcall\fR are passed +to the subprocedure by reference; the datatypes of the actual arguments are +unknown but the number and datatypes of the arguments must match those +expected by the subprocedure. The arguments are restricted to variables, +constants, arrays, and array elements of datatypes \fBcsilrd\fR. The magic +integer \fIprocedure\fR must have been obtained by a prior call to \fBzlocpr\fR. +.ih +RETURN VALUE +Any of the arguments may be used to return a value depending on the +significance of the argument to the subprocedure called. +The procedure itself may not return a value, i.e., \fBzcall\fR may not +be used to call a function. +.ih +NOTES +The arguments to \fIprocedure\fR must not be Fortran CHARACTER variables +or constants, external procedures, or objects of datatype complex. +.ih +SEE ALSO +zlocpr +.endhelp diff --git a/unix/os/doc/zclcpr.hlp b/unix/os/doc/zclcpr.hlp new file mode 100644 index 00000000..55574785 --- /dev/null +++ b/unix/os/doc/zclcpr.hlp @@ -0,0 +1,33 @@ +.help zclcpr May84 "System Interface" +.ih +NAME +zclcpr -- close or disconnect a connected subprocess +.ih +SYNOPSIS +.nf +zclcpr (pid, exit_status) + +int pid # process id (a magic integer) +int exit_status # termination code from process +.fi +.ih +DESCRIPTION +Disconnect a subprocess previously connected with \fBzopcpr\fR, +i.e., close the IPC channels and wait for the subprocess to terminate. +Control does not return until the child process has terminated. + +If the child process attempts to write to the parent after the IPC channels +have been closed the X_IPC exception will be raised in the child process. +If the child attempts to read from the parent after the parent has +disconnected, the child will see EOF on the read and will shutdown. +.ih +RETURN VALUE +The integer termination code of the child process is returned in +\fIexit_status\fR. A status of OK (zero) indicates normal termination. +ERR is returned for an illegal \fIpid\fR. +If the child terminates abnormally, i.e., if a panic exit occurs, the positive +integer error code of the error which caused process termination is returned. +.ih +SEE ALSO +zopcpr, zintpr, zxwhen +.endhelp diff --git a/unix/os/doc/zcldir.hlp b/unix/os/doc/zcldir.hlp new file mode 100644 index 00000000..1503611c --- /dev/null +++ b/unix/os/doc/zcldir.hlp @@ -0,0 +1,28 @@ +.help zcldir May84 "System Interface" +.ih +NAME +zcldir -- close a directory file +.ih +SYNOPSIS +.nf +zcldir (chan, status) + +int chan # OS channel of directory file +int status +.fi +.ih +DESCRIPTION +Close a directory file previously opened for reading with \fBzopdir\fR. +.ih +RETURN VALUE +ERR is returned in \fIstatus\fR for an illegal \fIchan\fR. OK is returned +if the operation is successful. +.ih +NOTES +A directory file is not accessed as an ordinary file; the significance of +\fIchan\fR is unknown to the high level code and need not refer to a physical +host i/o channel. +.ih +SEE ALSO +zopdir, zgfdir +.endhelp diff --git a/unix/os/doc/zcldpr.hlp b/unix/os/doc/zcldpr.hlp new file mode 100644 index 00000000..5e80700c --- /dev/null +++ b/unix/os/doc/zcldpr.hlp @@ -0,0 +1,38 @@ +.help zcldpr May84 "System Interface" +.ih +NAME +zcldpr -- close a detached process +.ih +SYNOPSIS +.nf +zcldpr (jobcode, killflag, exit_status) + +int jobcode # code by which job is known to system +int killflag # if YES, kill bkg job +int exit_status # exit status of bkg job +.fi +.ih +DESCRIPTION +If \fIkillflag\fR is NO, process execution will be suspended until +the background job terminates. If \fIkillflag\fR is YES the background +job is dequeued if it has not yet been run, or is killed if it is currently +executing. The integer \fIjobcode\fR is the magic number assigned the +job by the \fBzopdpr\fR primitive. +.ih +RETURN VALUE +ERR is returned for an illegal \fIjobcode\fR or for an attempt to kill +a job without the necessary permissions. If the operations completes +successfully the exit status of the process, i.e., OK or a positive integer +error code, is returned in \fIexit_status\fR. +.ih +NOTES +The CL calls this procedure whenever it detects that a background job has +terminated, since a background job may be run as a subprocess on some systems +and since it may be necessary to perform special actions after a subprocess has +terminated. The CL also calls this procedure whenever the user \fBkills\fR a +background job, or when the user wishes to \fBwait\fR for a background job to +terminate. +.ih +SEE ALSO +zopdpr +.endhelp diff --git a/unix/os/doc/zclsbf.hlp b/unix/os/doc/zclsbf.hlp new file mode 100644 index 00000000..1d436a59 --- /dev/null +++ b/unix/os/doc/zclsbf.hlp @@ -0,0 +1,32 @@ +.help zclsbf May84 "System Interface" +.ih +NAME +zclsbf -- close a binary file +.ih +SYNOPSIS +.nf +zclsbf (chan, status) + +int chan # OS channel of binary file +int status +.fi +.ih +DESCRIPTION +The binary file associated with the channel \fIchan\fR is closed, i.e., +the file is disassociated from the process which opened it and freed for +access by some other process, and the channel is freed for use with another +file. A binary file must be closed before process termination or the integrity +of the file is not guaranteed. +.ih +RETURN VALUE +ERR is returned in \fIstatus\fR for an illegal \fIchan\fR. OK is returned +if the operation is successful. +.ih +NOTES +The IRAF Main guarantees that all files will be closed prior to process +shutdown. The Main will also close all open files at program termination +unless the program explicitly indicates that a file is to be left open. +.ih +SEE ALSO +zopnbf, zfiobf +.endhelp diff --git a/unix/os/doc/zclstx.hlp b/unix/os/doc/zclstx.hlp new file mode 100644 index 00000000..1eeb184e --- /dev/null +++ b/unix/os/doc/zclstx.hlp @@ -0,0 +1,35 @@ +.help zclstx May84 "System Interface" +.ih +NAME +zclstx -- close a text file +.ih +SYNOPSIS +.nf +zclstx (chan, status) + +int chan # OS channel of text file +int status +.fi +.ih +DESCRIPTION +The text file associated with the channel \fIchan\fR is closed, i.e., +the file is disassociated from the process which opened it and freed for +access by some other process, and the channel is freed for use with another +file. A text file must be closed before process termination or the integrity +of the file is not guaranteed. +.ih +RETURN VALUE +ERR is returned in \fIstatus\fR for an illegal \fIchan\fR. OK is returned +if the operation is successful. +.ih +NOTES +FIO does not assume that \fBzclstx\fR will flush any buffered output; +FIO will explicitly flush buffered output before calling \fBzclstx\fR to +close a file. The IRAF Main guarantees that all files will be closed prior +to process shutdown. The Main will also close all open files at program +termination unless a program explicitly indicates that a file is to be +left open. +.ih +SEE ALSO +zopntx, zfiotx +.endhelp diff --git a/unix/os/doc/zfacss.hlp b/unix/os/doc/zfacss.hlp new file mode 100644 index 00000000..206d05e4 --- /dev/null +++ b/unix/os/doc/zfacss.hlp @@ -0,0 +1,37 @@ +.help zfacss May84 "System Interface" +.ih +NAME +zfacss -- determine the accessibility and type of a file +.ih +SYNOPSIS +.nf +zfacss (osfn, mode, type, status) + +packed char osfn[] # host filename +int mode # access mode to be checked +int type # file type to be tested +int status # is file accessible as specified +.fi +.ih +DESCRIPTION +Determine if a file is accessible with the indicated access modes and whether +or not the file is of the indicated type. If either \fImode\fR or \fItype\fR is +zero it is not checked; if both are zero, only the existence of the file +is checked. Legal access modes are 0, READ_ONLY, READ_WRITE, WRITE_ONLY, and +APPEND. Legal file types are 0, TEXT_FILE, and BINARY_FILE. +.ih +RETURN VALUE +YES is returned if the file is accessible with the indicated mode and type; +NO is returned otherwise. +.ih +NOTES +On some systems (e.g. UNIX) it is necessary to actually read part of the file +to test whether or not it is a text file, since the OS does not discriminate +between text and binary files. Hence use of \fBzfacss\fR to check the file +type is an expensive operation on some systems. +There is no guarantee that the accessibility of a file will not change between +the time \fBzfacss\fR is called and before the file is opened. +.ih +SEE ALSO +zfinfo, zfprot +.endhelp diff --git a/unix/os/doc/zfaloc.hlp b/unix/os/doc/zfaloc.hlp new file mode 100644 index 00000000..c22efb62 --- /dev/null +++ b/unix/os/doc/zfaloc.hlp @@ -0,0 +1,34 @@ +.help zfaloc May84 "System Interface" +.ih +NAME +zfaloc -- preallocate space for a binary file +.ih +SYNOPSIS +.nf +zfaloc (osfn, nbytes, status) + +packed char osfn[] # host filename +long nbytes # file size in bytes +int status +.fi +.ih +DESCRIPTION +Create and allocate storage for a file of the indicated size. The actual amount +of storage allocated will be the requested size rounded up to an integral +number of device blocks. Contiguous storage will be allocated if possible. +File data is unitialized. +.ih +RETURN VALUE +ERR is returned if the file cannot be created or if the requested amount of +storage cannot be allocated. OK is returned if there are no errors. +.ih +BUGS +On some systems it is necessary to physically write to a file to allocate +storage; preallocation of file storage is very expensive on such systems and +should be avoided. On other systems storage will appear to have been +allocated but physical storage will not be allocated until file blocks are +accessed at run time. +.ih +SEE ALSO +A discussion of the static file driver and imagefile access. +.endhelp diff --git a/unix/os/doc/zfchdr.hlp b/unix/os/doc/zfchdr.hlp new file mode 100644 index 00000000..08b6c4ca --- /dev/null +++ b/unix/os/doc/zfchdr.hlp @@ -0,0 +1,29 @@ +.help zfchdr May84 "System Interface" +.ih +NAME +zfchdr -- change the current working directory +.ih +SYNOPSIS +.nf +zfchdr (new_directory, status) + +packed char new_directory[] # osfn of new directory +int status +.fi +.ih +DESCRIPTION +The current working directory is changed to the directory specified +by the packed OS pathname given as the first argument. +.ih +RETURN VALUE +ERR is returned if the new directory does not exist or cannot be accessed. +OK is returned if the operation is successful. +.ih +NOTES +On a host system with a flat directory structure the kernel will have to map +the hierarchical directory structure assumed by IRAF onto the linear directory +structure provided by the host. +.ih +SEE ALSO +zfpath, zopdir, zgfdir, zfsubd +.endhelp diff --git a/unix/os/doc/zfdele.hlp b/unix/os/doc/zfdele.hlp new file mode 100644 index 00000000..5016a2b5 --- /dev/null +++ b/unix/os/doc/zfdele.hlp @@ -0,0 +1,29 @@ +.help zfdele May84 "System Interface" +.ih +NAME +zfdele -- delete a file +.ih +SYNOPSIS +.nf +zfdele (osfn, status) + +packed char osfn[] # host filename +int status +.fi +.ih +DESCRIPTION +The named file is deleted. +.ih +RETURN VALUE +ERR is returned if the file does not exist or cannot be deleted. +OK is returned if the operation is successful. +.ih +NOTES +A protected file cannot be deleted. FIO checks for file protection before +calling the kernel to delete a file. FIO will not attempt to delete a file +while the file is open by the current process. If an attempt is made to +delete a file which is open by another process the result is system dependent. +.ih +SEE ALSO +zfprot +.endhelp diff --git a/unix/os/doc/zfgcwd.hlp b/unix/os/doc/zfgcwd.hlp new file mode 100644 index 00000000..11fb5735 --- /dev/null +++ b/unix/os/doc/zfgcwd.hlp @@ -0,0 +1,26 @@ +.help zfgcwd May84 "System Interface" +.ih +NAME +zfgcwd -- get current working directory +.ih +SYNOPSIS +.nf +zfgcwd (pathname, maxch, status) + +packed char pathname[maxch] # receives pathname of cwd +int maxch, status +.fi +.ih +DESCRIPTION +The pathname of the current working directory is returned as a packed string, +suitable for concatenation with a filename to produce the pathname of the file. +.ih +RETURN VALUE +ERR is returned if the output string overflows or if the name of the current +working directory cannot be obtained for some reason. If the operation is +successful the number of characters in the output string is returned, +excluding the EOS delimiter. +.ih +SEE ALSO +zfxdir, zfsubd, zfpath +.endhelp diff --git a/unix/os/doc/zfinfo.hlp b/unix/os/doc/zfinfo.hlp new file mode 100644 index 00000000..7738de94 --- /dev/null +++ b/unix/os/doc/zfinfo.hlp @@ -0,0 +1,66 @@ +.help zfinfo May84 "System Interface" +.ih +NAME +zfinfo -- get directory information for the named file +.ih +SYNOPSIS +.nf +include + +zfinfo (osfn, out_struct, status) + +packed char osfn[] # host filename +long out_struct[LEN_FINFO] # output structure +int status +.fi +.ih +DESCRIPTION +A binary structure is returned describing the named file. + +.nf + struct finfo { + long fi_type # file type + long fi_size # file size, bytes + long fi_atime # time of last access + long fi_mtime # time of last modify + long fi_ctime # time of file creation + long fi_perm # file permission bits + char fi_owner[15] # name of file owner + } + +File types: + + FI_REGULAR 1 # ordinary file + FI_DIRECTORY 2 # directory file + FI_EXECUTABLE 3 # executable image + FI_SPECIAL 4 # terminals etc. +.fi + +The file owner name is returned as a packed string. Times are in long integer +seconds since midnight Jan 1, 1980 LST. File permissions are encoded in +bits 1-6 of \fIfi_perm\fR as follows: + +.nf + bit 1,2 owner r,w + bit 3,4 group r,w + bit 5,6 world r,w +.fi + +An ordinary file may be either a text file or a binary file. A directory file +is the entry for a subdirectory of the directory referenced by \fBzfinfo\fR. +An executable file is a file marked executable by the host (the exact +significance of an executable file is machine dependent). +Everything else is a special file. +.ih +RETURN VALUE +ERR is returned if the named file does not exist or cannot be accessed. +OK is returned if the operation is successful. +.ih +NOTES +\fBZfinfo\fR is not used to determine if a file is protected from deletion +or to determine whether a file is a text or binary file. \fBZfinfo\fR should +not be called to obtain information on an open file. +.ih +SEE ALSO +zfprot, zfacss +.endhelp diff --git a/unix/os/doc/zfiobf.hlp b/unix/os/doc/zfiobf.hlp new file mode 100644 index 00000000..96fd2022 --- /dev/null +++ b/unix/os/doc/zfiobf.hlp @@ -0,0 +1,53 @@ +.help zfiobf May84 "System Interface" +.ih +NAME +zfiobf -- binary file driver +.ih +SYNOPSIS +.nf +zopnbf (osfn, mode, chan) # open or create binary file +zclsbf (chan, status) # close binary file +zardbf (chan, buf, maxbytes, loffset) # asynchronous read +zawrbf (chan, buf, nbytes, loffset) # asynchronous write +zawtbf (chan, status) # wait for transfer +zsttbf (chan, param, lvalue) # get file/device status + +packed char osfn[] +char buf[] +int mode, chan, maxbytes, nbytes, param, status +long loffset, lvalue +.fi +.ih +DESCRIPTION +A binary file is an extendable array of machine bytes. There are two types +of binary files: \fBblocked\fR files, which are randomly accessible in chunks +the size of a device block, and \fBstreaming\fR binary files, which are +restricted to sequential access and characterized by a variable block size. +A binary file is effectively an extension of host memory, i.e., arbitrary +regions of memory may be written to a binary file and later restored +(possibly at a different location) without modification of the data. +Unlike the text file, there are no restrictions on the contents of a binary +file. +.ih +RETURN VALUES +When a binary file is opened the kernel assigns a channel to the file +and all subsequent file operations refer to the file by the channel number. +The asynchronous read and write primitives do not return a status value; +the number of bytes read or written or ERR is returned in a subsequent +call to \fBzawtbf\fR. Every i/o transfer must be followed by a call to +\fBzawtbf\fR. Only one transfer is permitted on a file at a time. +.ih +NOTES +If a file is accessed by name (rather than by channel number) while the file +is open the results are machine dependent. +If a file is blocked reads and writes must be aligned on block boundaries; +file offsets are one-indexed. A binary file may be extended by writing at +EOF or by overwriting EOF. All blocks but the last in a blocked file are the +same size; the last block may be partially full. A write to a streaming +file appends a new block of size \fInbytes\fR to the file; successive blocks +may vary in size. Each read from a streaming file returns a single variable +length block. +.ih +SEE ALSO +zfiotx, manual pages for the individual routines +.endhelp diff --git a/unix/os/doc/zfiolp.hlp b/unix/os/doc/zfiolp.hlp new file mode 100644 index 00000000..d3c4167e --- /dev/null +++ b/unix/os/doc/zfiolp.hlp @@ -0,0 +1,54 @@ +.help zfiolp May84 "System Interface" +.ih +NAME +zfiolp -- line printer driver +.ih +SYNOPSIS +.nf +zopnlp (osfn, mode, chan) # open line printer +zclslp (chan, status) # close line printer +zardlp (chan, buf, maxbytes, loffset) # asynchronous read +zawrlp (chan, buf, nbytes, loffset) # asynchronous write +zawtlp (chan, status) # wait for transfer +zsttlp (chan, param, lvalue) # get file/device status + +packed char osfn[] +char buf[] +int mode, chan, maxbytes, nbytes, param, status +long loffset, lvalue +.fi +.ih +DESCRIPTION +The line printer devices are interfaced as binary files. Except where noted +herein, the line printer driver is functionally equivalent to the driver for +an ordinary streaming binary file. + +A line printer device is opened with \fBzopnlp\fR and closed with \fBzclslp\fR. +The name of the device to be opened is given by \fIosfn\fR and is host system +dependent. The names of the printer devices recognized by \fBzopnlp\fR must +agree with those in the CL environment list and in the printer capability file +\fBdev$printcap\fR. Only the APPEND and WRITE_ONLY modes are supported by +most printer devices. Depending on the location and characteristics of the +device, \fBzopnlp\fR may or may not open the device directly. Often a +binary spoolfile is opened instead, and the spoolfile is (asynchronously) +disposed of to the physical device when \fBzclspr\fR is called. + +Binary data is copied to the printer device without modification, hence all +control functions (including newline) must have been fully translated into +device dependent control sequences by the time \fBzawrlp\fR is called to +output the data to the device. Either character data or binary bitmap data +(graphics) may be transmitted to a printer device. +.ih +NOTES +If the printer device is very fast it will not be desirable to spool printer +output when printing large text files due to the additional expense of writing +a large spoolfile. A better approach is to write directly to the device if +it is available, spooling only if the device is already in use at \fBzopnlp\fR +time. A second virtual device can be defined which writes to the same +physical device but which always spools the output. If the line printer +device is shared in a local area network it may be necessary to spool the +output and copy the spoolfile to a remote host for disposal to the printer. +.ih +SEE ALSO +zfiobf, lpopen, dev$printcap, manual pages for the binary file driver +.endhelp diff --git a/unix/os/doc/zfiomt.hlp b/unix/os/doc/zfiomt.hlp new file mode 100644 index 00000000..717524a4 --- /dev/null +++ b/unix/os/doc/zfiomt.hlp @@ -0,0 +1,65 @@ +.help zfiomt May84 "System Interface" +.ih +NAME +zfiomt -- magtape driver primitives +.ih +SYNOPSIS +.nf +zzopmt (drive, density, mode, oldrec, oldfile, newfile, chan) +zzclmt (chan, mode, nrecords, nfiles, status) +zzrdmt (chan, buf, maxbytes) +zzwrmt (chan, buf, nbytes) +zzwtmt (chan, nrecords, nfiles, status) +zzrwmt (chan, status) + +int drive, density, mode, oldrec, oldfile, newfile, chan +int nfiles, maxbytes, nbytes, nrecords, nfiles, status +char buf[] +.fi +.ih +DESCRIPTION +Magnetic tape is interfaced to FIO as a streaming binary file. +The conventional set of six binary file driver routines (suffix "mt") are +used, but due to the complexity of the driver the machine dependence has +been further concentrated into the six primitives shown above. The standard +driver routines are machine independent and are included in the standard +distribution. + +Since the magtape primitives are unique, separate manual pages are provided +for each primitive. The most complex primitive is \fBzzopmt\fR, which opens +a single file on a magtape device. To maximize the machine and device +independence of magtape i/o, only a single file may be accessed per open. +Aside from the rewind primitive \fBzzrwmt\fR there are no explicit tape +positioning commands. The tape is positioned ready to read or write the +first record of a file by \fBzzopmt\fR, and thereafter the tape moves only +when it is read or written. All magtape i/o is sequential, and a file may +be opened for reading or for writing but not for both. +.ih +RETURN VALUES +The \fIchan\fR and \fIstatus\fR parameters are identical to those for any +other streaming binary file. Magtape i/o is unique in that the high level +code is charged with keeping track of the position of the tape at all times. +The \fInrecords\fR and \fInfiles\fR return values tell the high level code +how much the tape was moved each time a primitive is called. +.ih +NOTES +To IRAF programs a magtape is a sequence of zero or more files separated +by end of file marks (filemarks, EOF) with an end of tape mark (tapemark, EOT) +following the last file on the tape. Each file consists of one or more +data blocks. Successive data blocks may vary in size; very short blocks +and odd size blocks (block size not commensurate with the size of an SPP char) +can cause problems. The high level code tries hard to deal with odd size +blocks and such but success is not guaranteed. A tapemark is a double end +of file mark. As far as the i/o system is concerned tapes are unlabeled +and files do not have headers; everything but filemarks and tapemarks is data. + +There is no explicit provision for multivolume sets. It is assumed that either +the host system or the kernel will provide the necessary functionality to deal +with multivolume sets. We assume that if physical end of tape is encountered +while reading or writing a tape block the process of informing the operator to +mount the new volume, rereading or rewriting the block, etc., will be performed +transparently to the high level code. +.ih +SEE ALSO +zfiobf, mtopen +.endhelp diff --git a/unix/os/doc/zfiopr.hlp b/unix/os/doc/zfiopr.hlp new file mode 100644 index 00000000..555deccd --- /dev/null +++ b/unix/os/doc/zfiopr.hlp @@ -0,0 +1,58 @@ +.help zfiopr May84 "System Interface" +.ih +NAME +zfiopr -- IPC driver +.ih +SYNOPSIS +.nf +zopnpr (osfn, mode, chan) # ** NOT USED ** +zclspr (chan, status) # ** NOT USED ** +zardpr (chan, buf, maxbytes, loffset) # asynchronous read +zawrpr (chan, buf, nbytes, loffset) # asynchronous write +zawtpr (chan, status) # wait for transfer +zsttpr (chan, param, lvalue) # get file/device status + +packed char osfn[] +char buf[] +int mode, chan, maxbytes, nbytes, param, status +long loffset, lvalue +.fi +.ih +DESCRIPTION +The IPC driver is used to read and write the inter-process communications +channels connecting parent and child processes. Except where noted herein, +the specifications of the IPC driver are equivalent to those of the ordinary +streaming binary file driver. + +The \fBzopnpr\fR and \fBzclspr\fR primitives are not used by the IPC driver +and should not be supplied. The process connect and disconnect primitives +\fBzopcpr\fR and \fBzclcpr\fR are used to open and close the IPC channels +to a subprocess and perform other process control functions as well. + +The IPC channels, like all other streaming binary files, read and write data +blocks. Thus, if process A calls \fBzawrpr\fR to write a binary block of length +N bytes into an IPC channel, an N byte block will be returned by \fBzardpr\fR +to process B at the other end of the channel. Data blocks may be queued +in a channel until the storage capacity of the channel is reached. +If process A writes an N byte block and an M byte block into a channel with +successive \fBzawrpr\fR calls, process B will read an N byte block and an M +byte block in successive \fBzardpr\fR calls. + +If a process tries to write into a full channel process execution will be +suspended until enough data has been read from the channel to permit +completion of the write. If a process tries to read from an empty channel +it will be suspended until the process at the other end writes into the +channel or until the writing process closes the channel, in which case the +reader sees EOF. If the IPC driver is fully asynchronous process execution +will not be suspended until \fBzawtpr\fR is called. The wait primitive +returns when the data block has been written into the channel, rather than +when the data has been read by the process at the other end. +.ih +NOTES +If a process writes into a channel with no reader (the reading process has +died, e.g., in a \fBzpanic\fR exit), the exception X_IPC will be raised in +the writing process. This is necessary to avoid deadlock. +.ih +SEE ALSO +zfiobf, zopcpr, zclcpr, zintpr, zxwhen +.endhelp diff --git a/unix/os/doc/zfiosf.hlp b/unix/os/doc/zfiosf.hlp new file mode 100644 index 00000000..e618031c --- /dev/null +++ b/unix/os/doc/zfiosf.hlp @@ -0,0 +1,51 @@ +.help zfiosf May84 "System Interface" +.ih +NAME +zfiosf -- static file driver +.ih +SYNOPSIS +.nf +zopnsf (osfn, mode, chan) # open static file +zclssf (chan, status) # close static file +zardsf (chan, buf, maxbytes, loffset) # asynchronous read +zawrsf (chan, buf, nbytes, loffset) # asynchronous write +zawtsf (chan, status) # wait for transfer +zsttsf (chan, param, lvalue) # get file/device status + +packed char osfn[] +char buf[] +int mode, chan, maxbytes, nbytes, param, status +long loffset, lvalue +.fi +.ih +DESCRIPTION +The static file driver is used to randomly access binary files which do not +change in size once created, hence the term static. Except where noted +herein, the specifications of the static file driver are equivalent to those +of the ordinary random access binary file driver. + +A static binary file is created by the kernel primitive \fBzfaloc\fR, +hence \fBzopnsf\fR cannot be used to create a new file (NEW_FILE mode is not +supported). The asynchronous read and write primitives behave conventionally +except that writing at EOF or overwriting EOF is not permitted. +.ih +NOTES +The static file driver should provide the lowest possible level of binary +file i/o for maximum efficiency. Since the file size is known at file creation +time it is often possible to allocate a contiguous file. Given a contiguous or +nearly contiguous file which does not change in size it is sometimes possible +to bypass the host files system once the file has been created, i.e., to +map the \fBzfiosf\fR primitives directly into calls to the disk driver on the +host machine. + +On a virtual memory machine it may also be possible to map the static file +into virtual memory, i.e., defer i/o until the file data is actually used. +If a virtual memory interface is implemented \fBzardsf\fR will remap pages +of memory, \fBzawrsf\fR will update pages of memory, \fBzawtsf\fR will do +nothing but return status, and \fBzclssf\fR will update and unmap any +mapped pages and close the file. See the reference manual for further +discussion of static file implementation strategies. +.ih +SEE ALSO +zfiobf, manual pages for the binary file driver +.endhelp diff --git a/unix/os/doc/zfiotx.hlp b/unix/os/doc/zfiotx.hlp new file mode 100644 index 00000000..e65ce6fe --- /dev/null +++ b/unix/os/doc/zfiotx.hlp @@ -0,0 +1,44 @@ +.help zfiotx May84 "System Interface" +.ih +NAME +zfiotx -- text file driver +.ih +SYNOPSIS +.nf +zopntx (osfn, mode, chan) # open or create text file +zclstx (chan, status) # close text file +zgettx (chan, text, maxch, status) # get next record +zputtx (chan, text, nchars, status) # put record +zflstx (chan, status) # flush output +znottx (chan, loffset) # note file position +zsektx (chan, loffset, status) # seek to a line +zstttx (chan, param, lvalue) # get file status + +packed char osfn[] +char text[] +int mode, chan, maxch, nchars, status, param +long loffset, lvalue +.fi +.ih +DESCRIPTION +All text file i/o is via these primitives. The detailed specifications +of the individual routines are given in separate manual pages. +A text file must be opened or created with \fBzopntx\fR before any i/o +can take place. Text file i/o is record (line) oriented and is sequential +in nature. Character data is maintained in ASCII SPP chars above the kernel +and in the host character format below the kernel. +The newline character delimits each line of text. +Seeking is permitted prior to a write but only to the beginning of a line +or to BOF or EOF. The seek offset of a line may only be determined by a +prior call to \fBznottx\fR when actually reading or writing the file. +Writing is permitted only at EOF. Output is assumed to be buffered. +.ih +RETURN VALUES +Once a file is opened all references to the file are by the channel number +\fIchan\fR, a magic integer. A file may not be accessed by name while it +is open. The i/o primitives return the number of characters read or written +as the status value; 0 is returned when reading at EOF. +.ih +SEE ALSO +zfioty, the manual pages for the individual routines +.endhelp diff --git a/unix/os/doc/zfioty.hlp b/unix/os/doc/zfioty.hlp new file mode 100644 index 00000000..de0f0752 --- /dev/null +++ b/unix/os/doc/zfioty.hlp @@ -0,0 +1,75 @@ +.help zfioty May84 "System Interface" +.ih +NAME +zfioty -- terminal driver +.ih +SYNOPSIS +.nf +zopnty (osfn, mode, chan) # open terminal +zclsty (chan, status) # close terminal +zgetty (chan, text, maxch, status) # get next record +zputty (chan, text, nchars, status) # put record +zflsty (chan, status) # flush output +znotty (chan, loffset) # note offset +zsekty (chan, loffset, status) # seek to offset +zsttty (chan, param, lvalue) # get file status + +packed char osfn[] +char text[] +int mode, chan, maxch, nchars, status, param +long loffset, lvalue +.fi +.ih +DESCRIPTION +Except where noted herein, the terminal driver is functionally equivalent to +the text file driver \fBzfiotx\fR. Terminal data is normally quite volatile, +hence the \fBznotty\fR and \fBzsekty\fR functions are generally not usable with +terminals. If seeking is not supported on a terminal \fBznotty\fR may return +anything, and \fBzsekty\fR will return ERR. + +Terminal input is normally accumulated in the host system terminal driver +and returned by \fBzgetty\fR a line at a time. If \fImaxch\fR is greater than +one but less than the length of the line typed at the terminal, the line is +buffered by the kernel and substrings are returned in successive calls to +\fBzgetty\fR until the line is exhausted, just as for any other text file. +Control characters may be intercepted by the host driver and interpreted +as line editing commands, commands to change the driver state, and so on. + +If \fBzgetty\fR is called with \fImaxch=1\fR the terminal is put into raw +character mode. In this mode \fBzgetty\fR returns each character as it is +typed, control characters have no special significance (as far as possible), +and characters are not echoed to the terminal. The switch to character mode +will only occur at the beginning of a line, i.e., any character data buffered +internally in \fBzgetty\fR will be exhausted before switching to character +mode. A subsequent call with \fImaxch\fR greater than one causes a switch +back to line mode. + +There is nothing corresponding to character mode for \fBzputty\fR. To write +to the terminal a character at a time one need only call \fBzflsty\fR after +each character is written with \fBzputty\fR. All control characters except +tab and newline (linefeed) may be sent to the terminal without modification. +Tab characters may be expanded by the driver, and newline is converted into +carriage return linefeed upon output. +.ih +NOTES +Separate channels are used for reading and writing to simplify buffering +and to provide device independence. The access modes READ_WRITE and NEW_FILE +are not supported for terminals. A terminal file, unlike most other files, +may be simultaneously opened on two different channels if both read and write +access is desired. + +The mode switch on \fImaxch\fR seems like a potentially unwanted side effect +but this is not the case since \fBzgetty\fR is called only by FIO. +In normal use FIO will always call \fBzgetty\fR with \fImaxch\fR equal to +the size of the FIO line buffer, i.e., SZ_LINE. This is the case even if the +calling program calls \fBgetc\fR to read a character at a time. At the FIO +level the switch to and from character mode is possible only by an explicit +call to \fBfset\fR to change the default behavior of FIO for the file. +When character mode is in effect on a text file opened for reading (be it a +terminal or not) FIO merely fakes itself into thinking the size of the FIO +buffer is 1 char, forcing a call to \fBzgetty\fR for each character read from +the file. +.ih +SEE ALSO +zfiotx, manual pages for the text file driver. +.endhelp diff --git a/unix/os/doc/zflstx.hlp b/unix/os/doc/zflstx.hlp new file mode 100644 index 00000000..7abbd8a8 --- /dev/null +++ b/unix/os/doc/zflstx.hlp @@ -0,0 +1,33 @@ +.help zflstx May84 "System Interface" +.ih +NAME +zflstx -- flush any buffered text file output +.ih +SYNOPSIS +.nf +zflstx (chan, status) + +int chan # OS channel of text file +int status +.fi +.ih +DESCRIPTION +Any output data buffered within the kernel or host system is flushed to +the output device. +.ih +RETURN VALUE +ERR is returned in the event of a write error. OK is returned if the flush +is successful. There is no way to tell if any data was actually written to +the output device. +.ih +NOTES +FIO assumes that text file output is buffered and that \fBzflstx\fR must be +called to ensure that data written with \fBzputtx\fR is actually sent to +the device. When \fBzflstx\fR is called all buffered text should be output +whether or not a newline has been seen. FIO assumes that it can build up +an output line a character at a time, calling \fBzputtx\fR followed by +\fBzflstx\fR for each individual character. +.ih +SEE ALSO +zfiotx, zputtx +.endhelp diff --git a/unix/os/doc/zfmkcp.hlp b/unix/os/doc/zfmkcp.hlp new file mode 100644 index 00000000..bf5094ca --- /dev/null +++ b/unix/os/doc/zfmkcp.hlp @@ -0,0 +1,40 @@ +.help zfmkcp May84 "System Interface" +.ih +NAME +zfmkcp -- make a zero length copy of a file +.ih +SYNOPSIS +.nf +zfmkcp (osfn, new_osfn, status) + +packed char osfn[] # name of original file +packed char new_osfn[] # name of new file +int status +.fi +.ih +DESCRIPTION +A zero length file is created which inherits all the machine dependent +attributes (so far as is possible) of the original file. No file data +is copied. The new file need not reside in the same directory as the +original file. +.ih +RETURN VALUE +ERR is returned if the original file cannot be accessed, if the new file +cannot be created, or if the file cannot be accessed as either a text or binary +file. OK is returned if the operation is successful. +.ih +NOTES +The newly created file is normally opened for writing as a text or binary +file immediately after creation with \fBzfmkcp\fR. The IRAF system has +no knowledge of the machine dependent attributes of a file, e.g., execute +permission, cross-directory links, special permissions, and so on. + +FIO ensures that a file will not already exist named \fInew_osfn\fR when +\fBzfmkcp\fR is called. If such a file exists and file clobber is enabled, +FIO will delete the file before calling \fBzfmkcp\fR. If file clobber +is disabled and a file already exists with the new name, FIO will take an +error action. +.ih +SEE ALSO +zopnbf, zopntx +.endhelp diff --git a/unix/os/doc/zfpath.hlp b/unix/os/doc/zfpath.hlp new file mode 100644 index 00000000..920caab3 --- /dev/null +++ b/unix/os/doc/zfpath.hlp @@ -0,0 +1,32 @@ +.help zfpath May84 "System Interface" +.ih +NAME +zfpath -- convert an OSFN into an absolute pathname +.ih +SYNOPSIS +.nf +zfpath (osfn, pathname, maxch, status) + +char osfn[ARB] # OS filename +char pathname[maxch] # absolute pathname equiv. of OSFN +int maxch, status +.fi +.ih +DESCRIPTION +Return the absolute pathname equivalent of an OS filename. An absolute pathname +is an OSFN which does not depend on the current working directory. +If the argument \fIosfn\fR is null the pathname of the current working +directory is returned. +.ih +RETURN VALUE +ERR is returned if the translation cannot be performed for any reason, +or if the output string overflows. If the operation is successful the +number of characters in the output string is returned. +.ih +NOTES +This primitive is normally coded in SPP since it does not communicate +with the host system. Character string arguments are therefore not packed. +.ih +SEE ALSO +zfxdir, zfsubd +.endhelp diff --git a/unix/os/doc/zfprot.hlp b/unix/os/doc/zfprot.hlp new file mode 100644 index 00000000..dd989268 --- /dev/null +++ b/unix/os/doc/zfprot.hlp @@ -0,0 +1,47 @@ +.help zfprot May84 "System Interface" +.ih +NAME +zfprot -- set, remove, or query file delete protection +.ih +SYNOPSIS +.nf +zfprot (osfn, protflag, status) + +packed char osfn[ARB] # OS filename +int protflag # operation to be performed +int status +.fi +.ih +DESCRIPTION +A protected file cannot be deleted, accidentally or otherwise. +Protecting a file does not remove write permission. +File protection is set, removed, or queried as specified by the \fIprotflag\fR +argument, which has the following values: + +.nf + REMOVE_PROTECTION 0 + SET_PROTECTION 1 + QUERY_PROTECTION 2 +.fi + +It is not an error to attempt to protect a file which is already protected, +nor is it an error to attempt to remove protection from a file which is not +protected. +.ih +RETURN VALUE +OK is returned if a set_protection or remove_protection operation is +successful. YES (protected) or NO (not protected) is returned in response +to a query. ERR is returned if the named file does not exist or if +the operation cannot be performed. +.ih +NOTES +FIO will query for file protection before attempting to delete a file. +If the host system does not provide file protection facilities they can +often be faked by creating a hidden file in the same directory; the existence +of the file will indicate that the file is protected. If the hidden file +technique is used, the hidden file should not be seen when the directory +is read by the high level code. +.ih +SEE ALSO +zfdele +.endhelp diff --git a/unix/os/doc/zfrnam.hlp b/unix/os/doc/zfrnam.hlp new file mode 100644 index 00000000..3a253e66 --- /dev/null +++ b/unix/os/doc/zfrnam.hlp @@ -0,0 +1,40 @@ +.help zfrnam May84 "System Interface" +.ih +NAME +zfrnam -- rename a file +.ih +SYNOPSIS +.nf +zfrnam (old_osfn, new_osfn, status) + +packed char old_osfn[] # OS name of existing file +packed char new_osfn[] # new OS name of file +int status +.fi +.ih +DESCRIPTION +The name of file \fIold_osfn\fR is changed to \fInew_osfn\fR. +All file attributes are preserved by the rename operation. +.ih +RETURN VALUE +ERR is returned if the old file does not exist or if the rename operation +cannot be performed. OK is returned if the operation is successful. +If the operation is unsuccessful the original file is not affected in any +way. +.ih +NOTES +Ideally the rename operation should be successful even if the new filename +does not reference the same directory as the old filename, allowing a file +to be moved from one directory to another without physically copying the +file. If this is not possible ERR should be returned and the high level +code (e.g., \fBsystem.movefiles\fR) must physically copy the file. + +FIO ensures that a file will not already exist named \fInew_osfn\fR when +\fBzfrnam\fR is called. If such a file exists and file clobber is enabled, +FIO will delete the file before calling \fBzfrnam\fR. If file clobber +is disabled and a file already exists with the new name, FIO will take an +error action. File protection does not prevent renaming a file. +.ih +SEE ALSO +zfprot, zfdele +.endhelp diff --git a/unix/os/doc/zfsubd.hlp b/unix/os/doc/zfsubd.hlp new file mode 100644 index 00000000..1e58f36b --- /dev/null +++ b/unix/os/doc/zfsubd.hlp @@ -0,0 +1,76 @@ +.help zfsubd May84 "System Interface" +.ih +NAME +zfsubd -- get host name of a subdirectory +.ih +SYNOPSIS +.nf +zfsubd (osdir, subdir, new_osdir, maxch, nchars) + +char osdir[ARB] # directory pathname +char subdir[ARB] # subdirectory of osdir +char new_osdir[maxch] # pathname of osdir/subdir +int maxch # maximum length of new_osdir +int nchars # length of new_osdir +.fi +.ih +DESCRIPTION +Given \fIosdir\fR, the machine dependent name of a host directory, +and \fIsubdir\fR, the filename of a subdirectory of \fIosdir\fR, \fBzfsubd\fR +returns the machine dependent name of the subdirectory. +The machine dependent directory specification \fInew_osdir\fR may be +concatenated with a filename to produce an OSFN, or may be used in another +call to \fBzfsubd\fR to generate the name of a directory lower in the hierarchy. + +If \fIosdir\fR is null the current working directory is assumed. +If \fIsubdir\fR is null or has the value "." \fIosdir\fR is either copied to +the output or modified as necessary to return a concatenatable directory +prefix string. If \fIsubdir\fR has the value ".." the name of the next +\fIhigher\fR directory is returned, i.e., the directory in which \fIosdir\fR +appears as a subdirectory. +.ih +RETURN VALUE +ERR is returned if the translation cannot be performed (but the existence of +the new directory is not checked). If the translation is successful the number +of characters in the string \fInew_osdir\fR is returned, excluding the EOS +delimiter. +.ih +NOTES +This primitive is used by FIO to convert subdirectory references in virtual +filenames into machine dependent directory specifications. +An arbitrary virtual pathname is translated by repeatedly calling \fBzfsubd\fR +to add successive / delimited subdirectory names into the OS directory name. +The new OS directory name is not necessarily an absolute pathname; on some +systems it may be a pathname relative to the current directory. If an absolute +pathname is desired an additional call should be made to \fBzfpath\fR to +convert \fInew_osdir\fR into an absolute pathname. +This primitive is normally coded in SPP hence all strings are normal SPP +character strings rather than packed Fortran strings. +.ih +EXAMPLE +Consider the following VFN: + + pkg$images/imdelete.x + +The logical directory "pkg" is defined in the environment as "iraf$pkg/". +Assume the host system is VMS and "iraf", the root directory of the IRAF +system, is defined as "dra0:[iraf]". Recursive expansion of logical +directories will result in the following virtual pathname: + + dra0:[iraf]pkg/images/imdelete.x + +FIO will next call \fBzfxdir\fR to extract the OSDIR "dra0:[iraf]", +followed by \fBzfsubd\fR to combine this OSDIR and the subdirectory +name "pkg" to produce the new OSDIR "dra0:[iraf.pkg]". The process is +repeated until the final OSFN is generated: + + dra0:[iraf.pkg.images]imdelete.x +.ih +BUGS +We assume that an OSFN can be generated by a simple concatenation of an OS +directory specification and a filename. This assumption is valid on all +systems we are familiar with, but may be false on some unfamiliar host. +.ih +SEE ALSO +zfxdir, zfpath +.endhelp diff --git a/unix/os/doc/zfxdir.hlp b/unix/os/doc/zfxdir.hlp new file mode 100644 index 00000000..19477bec --- /dev/null +++ b/unix/os/doc/zfxdir.hlp @@ -0,0 +1,31 @@ +.help zfxdir May84 "System Interface" +.ih +NAME +zfxdir -- extract OS directory prefix from OSFN +.ih +SYNOPSIS +.nf +zfxdir (osfn, osdir, maxch, status) + +char osfn[ARB] # OS filename +char osdir[maxch] # OS directory prefix +int maxch, status +.fi +.ih +DESCRIPTION +The OS directory prefix, if any, is extracted from the OS filename +and returned as \fIosdir\fR. +.ih +RETURN VALUE +ERR is returned if the output string overflows. If \fIosfn\fR is null +or does not contain a directory prefix a status of zero is returned, +otherwise the number of characters in the output string is returned. +If there is no directory prefix the null string is returned in \fIosdir\fR. +.ih +NOTES +This routine is normally written in SPP since it does not communicate with +the host system. Character string arguments are therefore not packed. +.ih +SEE ALSO +zfpath, zfsubd, zfgcwd +.endhelp diff --git a/unix/os/doc/zgettx.hlp b/unix/os/doc/zgettx.hlp new file mode 100644 index 00000000..2a5b6bb6 --- /dev/null +++ b/unix/os/doc/zgettx.hlp @@ -0,0 +1,57 @@ +.help zgettx May84 "System Interface" +.ih +NAME +zgettx -- get next line from a text file +.ih +SYNOPSIS +.nf +zgettx (chan, text, maxch, status) + +int chan # OS channel of file +char text[maxch] # output record buffer +int maxch # capacity of buffer +int status +.fi +.ih +DESCRIPTION +At most \fImaxch\fR chars are read from the next line of the text file +connected to channel \fIchan\fR into the buffer \fItext\fR. +A line of text is a sequence of zero or more characters terminated by the +\fBnewline\fR character (normally linefeed). If \fImaxch\fR is less than +the length of the line the next read will return the remainder of the line +or \fImaxch\fR characters, whichever is smaller. The newline character +counts as one character and is returned as the final character in \fItext\fR +when end of line is reached. The \fBzgettx\fR primitive always returns ASCII +character data unpacked into the SPP char array \fItext\fR. The \fItext\fR +array is not EOS delimited. +.ih +RETURN VALUE +ERR is returned for a read error or for an illegal call. If the read is +successful the number of characters read (including the newline) is returned +in \fIstatus\fR. When EOF is reached successive reads will return nothing, +i.e., the number of characters read will be zero. +.ih +NOTES +There is no fixed upper limit on the length of a line. In normal usage FIO +calls \fBzputtx\fR to write out the internal FIO fixed size line buffer +whenever it sees a newline in the output. If an applications program writes +a very long line, the line buffer in FIO will overflow and \fBzputtx\fR will +be called to write out the contents of the buffer without a newline terminator. +FIO will also write out a partial line when the output is explicitly flushed. +On input FIO uses the same fixed size line buffer, and several calls to +\fBzgettx\fR may be required to read a full line. + +If the host system does not use the ASCII character set \fBzgettx\fR will +convert characters to ASCII upon input. The full ASCII character set is +permitted, i.e., control characters may be embedded in the text. +.ih +BUGS +Individual IRAF and host system utilities may place their own limits on the +maximum length of a line of text. The lower bound on the size of a line +of text in IRAF programs is globally defined by the parameter SZ_LINE in +\fBiraf.h\fR and may easily be adjusted by the system installer. A sysgen +of the entire system is required as SZ_LINE is used everywhere. +.ih +SEE ALSO +zfiotx, zputtx +.endhelp diff --git a/unix/os/doc/zgfdir.hlp b/unix/os/doc/zgfdir.hlp new file mode 100644 index 00000000..7c5412cf --- /dev/null +++ b/unix/os/doc/zgfdir.hlp @@ -0,0 +1,37 @@ +.help zgfdir May84 "System Interface" +.ih +NAME +zgfdir -- get next filename from a directory +.ih +SYNOPSIS +.nf +zgfdir (chan, osfn, maxch, status) + +int chan # OS channel of directory file +packed char osfn[maxch] # output filename +int maxch, status +.fi +.ih +DESCRIPTION +The next machine dependent filename is returned from the directory file +connected to \fIchan\fR. Filenames are not returned in any particular order. +The filename is returned as an EOS delimited packed string with no newline. +.ih +RETURN VALUE +The number of characters in the filename excluding the EOS delimiter is +returned for a successful read. EOF is returned when the directory is +exhausted. ERR is returned if there is something wrong with \fIchan\fR, +if a read error occurs, or if the output string overflows. +.ih +NOTES +Although this primitive returns simple, raw host filenames, it will not +necessarily return all of the filenames in a directory. On a UNIX system +for example, filenames which begin with the character "." are skipped over +when reading from a directory. On a VMS system only the most recent version +of a file should be returned (with the version label stripped). The kernel +may employ hidden files for special purposes; normally these should be +hidden from the high level code and from the user. +.ih +SEE ALSO +zopdir, zcldir, zfinfo, zfacss +.endhelp diff --git a/unix/os/doc/zgtime.hlp b/unix/os/doc/zgtime.hlp new file mode 100644 index 00000000..37c45a49 --- /dev/null +++ b/unix/os/doc/zgtime.hlp @@ -0,0 +1,28 @@ +.help zgtime May84 "System Interface" +.ih +NAME +zgtime -- get clock and cpu times +.ih +SYNOPSIS +.nf +zgtime (clock_time, cpu_time) + +long clock_time # LST, long integer seconds +long cpu_time # cpu time consumed, milliseconds +.fi +.ih +DESCRIPTION +The \fBzgtime\fR primitive returns the local standard time (clock time) +in long integer seconds since midnight on January 1, 1980, and the +cpu time consumed by the calling process and all subproceses since process +creation in milliseconds. No allowance is made for time zones or daylight +savings time. +.ih +BUGS +When daylight savings time goes into effect there is a one hour interval +during which the time base is ambiguous. A different time base whould have +to be used for a distributed system spanning several time zones. +.ih +SEE ALSO +ztslee +.endhelp diff --git a/unix/os/doc/zgtpid.hlp b/unix/os/doc/zgtpid.hlp new file mode 100644 index 00000000..899c653f --- /dev/null +++ b/unix/os/doc/zgtpid.hlp @@ -0,0 +1,25 @@ +.help zgtpid May84 "System Interface" +.ih +NAME +zgtpid -- get unique process-id number +.ih +SYNOPSIS +.nf +zgtpid (pid) + +int pid # process id number +.fi +.ih +DESCRIPTION +The magic integer value by which the current process is known to the host +operating system is returned as the argument \fIpid\fR. The process-id +is used by all process control operators to uniquely identify the process +to be operated upon. +.ih +NOTES +The process id number is also used by \fBmktemp\fR to generate unique temporary +filenames. +.ih +SEE ALSO +zopcpr, zclcpr, zintpr, zopdpr, zcldpr +.endhelp diff --git a/unix/os/doc/zintpr.hlp b/unix/os/doc/zintpr.hlp new file mode 100644 index 00000000..6899c8e1 --- /dev/null +++ b/unix/os/doc/zintpr.hlp @@ -0,0 +1,34 @@ +.help zintpr May84 "System Interface" +.ih +NAME +zintpr -- interrupt a process +.ih +SYNOPSIS +.nf +zintpr (pid, exception, status) + +int pid # process id of process to be interrupted +int exception # exception to be raised +int status +.fi +.ih +DESCRIPTION +The indicated virtual exception is raised in the process associated with +the process-id \fIpid\fR. Currently only the interrupt exception X_INT +may be sent to a process. +.ih +RETURN VALUE +ERR is returned for a bad process-id. OK is returned if the operation +is successful. +.ih +NOTES +The exception X_INT is also raised by the host terminal driver when +the interrupt control sequence is typed by the user at the terminal. +If the identical exception cannot be raised by a user process it may +be possible to use a different exception and have the kernel map both +to X_INT. In principle it should be possible for a process to interrupt +itself, though this capability may be machine dependent. +.ih +SEE ALSO +zxwhen +.endhelp diff --git a/unix/os/doc/zlocpr.hlp b/unix/os/doc/zlocpr.hlp new file mode 100644 index 00000000..6136d34b --- /dev/null +++ b/unix/os/doc/zlocpr.hlp @@ -0,0 +1,35 @@ +.help zlocpr May84 "System Interface" +.ih +NAME +zlocpr -- get the entry point address of a procedure +.ih +SYNOPSIS +.nf +zlocpr (procedure, address) + +extern procedure() # external procedure +int address # address of the procedure +.fi +.ih +DESCRIPTION +The entry point address (EPA) of \fIprocedure\fR is returned in the integer +variable or integer array element \fIaddress\fR. +.ih +RETURN VALUE +The EPA of a procedure is a magic integer value. Two EPA values may be +compared for equality to determine if they refer to the same procedure, +and a procedure referenced by an EPA may be executed by passing the EPA +and any arguments to a \fBzcall\fR primitive. +.ih +NOTES +A legal EPA may not have the value NULL, which is reserved for flagging +uninitialized EPA variables. The Fortran 77 alternate return from subroutine +feature may not be used with \fBzlocpr\fR and \fBzcall\fR because it involves +an extra hidden argument on some systems. The alternate return feature is +inadvisable for other reasons as well and is forbidden in SPP programs. +Only untyped procedures are permitted, i.e., \fBzlocpr\fR may not be used +with functions. +.ih +SEE ALSO +zcall, zlocva +.endhelp diff --git a/unix/os/doc/zlocva.hlp b/unix/os/doc/zlocva.hlp new file mode 100644 index 00000000..1239fa40 --- /dev/null +++ b/unix/os/doc/zlocva.hlp @@ -0,0 +1,47 @@ +.help zlocva May84 "System Interface" +.ih +NAME +zlocva -- get the memory address of a variable +.ih +SYNOPSIS +.nf +zlocva (object, address) + +arb object # reference to variable +int address # value of the reference +.fi +.ih +DESCRIPTION +The memory address of \fIobject\fR in char storage units is returned as +the value of the integer variable \fIaddress\fR. The referenced object +may be a variable or array element of actual datatype \fBcsilrdx\fR. +The referenced object may \fInot\fR be a procedure or a Fortran character +variable. +.ih +RETURN VALUE +The memory address returned references the process logical address space +\fIin units of SPP chars\fR. No zero point is assumed. +.ih +NOTES +We assume that the maximum address in char units will fit into a signed +integer variable. The high level code assumes that it can do signed integer +comparisons and arithmetic operations (additions and subtractions) upon the +addresses returned by \fBzlocva\fR to check arrays for equality and overlap +and to compute offsets when generating pointers into Mem. +Negative addresses are permitted provided the signed arithmetic and +comparison operations work properly, i.e., provided the negative addresses +are assigned to the first half of the process logical address space. +The following relationship must hold: + +.nf + call locva (Memc[1], addr1) + call locva (Memc[2], addr2) + if (addr2 - addr1 == 1 for all possible locations of Memc) + locva conforms to the standard +.fi + +\fBMemc\fR is an SPP char array in the global common \fBMem\fR. +.ih +SEE ALSO +zmaloc, zlocpr +.endhelp diff --git a/unix/os/doc/zmain.hlp b/unix/os/doc/zmain.hlp new file mode 100644 index 00000000..3b0c4406 --- /dev/null +++ b/unix/os/doc/zmain.hlp @@ -0,0 +1,62 @@ +.help zmain May84 "System Interface" +.ih +NAME +zmain -- process main +.ih +SYNOPSIS +.nf +not applicable +.fi +.ih +DESCRIPTION +The process main is the procedure or code segment which first gains control +when a process is executed by the host system. The process main must determine +whether the process was called as a connected subprocess, as a detached process, +or by the host system. If spawned as a connected subprocess the standard input +and output of the process are connected to IPC channels leading to the parent +process, otherwise the devices to which the process channels are connected are +machine dependent. + +After connecting the process standard input, standard output, and standard +error output \fBzmain\fR calls the IRAF Main, an SPP procedure. +The calling sequence of the IRAF Main is as follows: + +.nf + main (inchan, outchan, driver, prtype, bkgfile, jobcode) + + int inchan # standard input channel + int outchan # standard output channel + int driver # EPA of device driver for channels + int prtype # process type code + packed char bkgfile[] # name of bkgfile, if detached process + int jobcode # bkg jobcode, if detached process +.fi + +The IPC driver, text file driver, and binary file driver are resident in +every process. The \fBdriver\fR argument is the entry point address of +the read primitive of the appropriate driver, as returned by \fBzlocpr\fR. +The process type code is selected from the following: + +.nf + PR_CONNECTED 1 # connected subprocess + PR_DETACHED 2 # detached subprocess + PR_HOST 3 # process run from host +.fi + +The process type determines the type of protocol to be used by the IRAF Main. +The background file and jobcode are used only if the process was spawned as +a detached process. +.ih +RETURN VALUE +None. +.ih +NOTES +Currently only the CL may be run as a detached process, and only ordinary +SPP processes may be run as connected subprocesses. Either may be run directly +by the host system. The CL uses a nonstandard Main. Error recovery is +handled entirely by the IRAF Main. +.ih +SEE ALSO +zfiopr, zfiotx, and the discussion of the process and IRAF mains in the +reference manual. +.endhelp diff --git a/unix/os/doc/zmaloc.hlp b/unix/os/doc/zmaloc.hlp new file mode 100644 index 00000000..7863741b --- /dev/null +++ b/unix/os/doc/zmaloc.hlp @@ -0,0 +1,71 @@ +.help zmaloc May84 "System Interface" +.ih +NAME +zmaloc -- allocate memory +.ih +SYNOPSIS +.nf +zmaloc (buffer, nbytes, status) + +int buffer # address of buffer +int nbytes # size of buffer +int status +.fi +.ih +DESCRIPTION +An uninitialized region of memory at least \fInbytes\fR in size is dynamically +allocated. The address of the newly allocated buffer in units of SPP chars +is returned in \fIbuffer\fR. +.ih +RETURN VALUE +XERR is returned in \fIstatus\fR if the buffer cannot be allocated. +XOK is returned if the operation is successful. +.ih +NOTES +The integer \fIbuffer\fR is a memory address in SPP char units with an +arbitrary zero point, i.e., the type of address returned by \fBzlocva\fR. +The high level code converts the buffer address into an offset into \fBMem\fR, +i.e., into an SPP pointer. + +.nf + char_pointer_into_Mem = buffer - zlocva(Memc) + 1 + Memc[char_pointer] = first char of buffer +.fi + +Since the buffer address is returned in char units the buffer must be aligned +to at least the size of a char; no greater degree of alignment is guaranteed +nor required. See the specifications of \fBzlocva\fR for additional information +about addresses and address arithmetic. + +If the host system does not provide buffer management primitives (heap +management facilities), but can dynamically allocate memory to a process, +it will be necessary to build a memory allocator. This is normally done +by dynamically changing the top of the process address space. The region +between the highest address allocated at process creation time and the +current top of the process address space is the region used by the heap. +A simple and adequate heap management technique is to implement the heap +as a circular singly linked list of buffers. Each buffer is preceded by +a pointer to the next buffer and a flag telling whether or not the buffer +is currently allocated. Successive unused buffers are periodically collected +together into a single large buffer to minimize fragmentation. A buffer is +allocated by searching around the circular list for either the first fit +or the best fit. If an unused buffer of sufficient size is not found, +additional physical memory is allocated to the process and linked into the +list. + +On a system which cannot dynamically allocate memory to a process it will be +necessary to statically allocate a large \fBMem\fR common. The heap +management algorithm described above will work just as effectively for a +static array as for a dynamic region. If a heap manager has to be coded for +more than one machine we should add a semi-portable version to the system +(all current IRAF target machines provide heap management facilities at the +host level so we have not coded a portable memory allocator). + +Dynamic memory allocation may be used in the kernel implementation as well +as in the portable system and applications code. In general it is necessary +to use the same memory allocator in both the kernel and the high level +code to avoid trashing memory. +.ih +SEE ALSO +zmfree, zraloc, zlocva +.endhelp diff --git a/unix/os/doc/zmfree.hlp b/unix/os/doc/zmfree.hlp new file mode 100644 index 00000000..6762eba4 --- /dev/null +++ b/unix/os/doc/zmfree.hlp @@ -0,0 +1,36 @@ +.help zmfree May84 "System Interface" +.ih +NAME +zmfree -- free memory +.ih +SYNOPSIS +.nf +zmfree (buffer, status) + +int buffer # buffer address +int status +.fi +.ih +DESCRIPTION +Free a buffer previously allocated with \fBzmaloc\fR or \fBzraloc\fR, +i.e., return the space so that it may be reused by the same process or by +another process. The integer argument \fIbuffer\fR must be the buffer +address returned by the primitive which originally allocated the buffer. +.ih +RETURN VALUE +ERR is returned if there is something wrong with \fIbuffer\fR. OK is returned +if the operation is successful. +.ih +NOTES +When a buffer is deallocated memory space may or may not be returned to the +host operating system depending upon the address of the buffer and upon the +characteristics of the host system. If physical memory space can be +efficiently allocated to a process at runtime it is desirable to immediately +return deallocated space to the host so that it may be reused by another +process. Otherwise the space will remain physically allocated to the process +but will be placed on the memory allocator free list so that it may be +reallocated in a subsequent call to \fBmalloc\fR. +.ih +SEE ALSO +zmaloc, zraloc, zlocva +.endhelp diff --git a/unix/os/doc/znottx.hlp b/unix/os/doc/znottx.hlp new file mode 100644 index 00000000..20317566 --- /dev/null +++ b/unix/os/doc/znottx.hlp @@ -0,0 +1,45 @@ +.help znottx May84 "System Interface" +.ih +NAME +znottx -- note position in text file for a later seek +.ih +SYNOPSIS +.nf +znottx (chan, loffset) + +int chan # OS channel of text file +long loffset # magic seek offset +.fi +.ih +DESCRIPTION +The absolute seek offset of the "current line" is returned in the long integer +variable \fIloffset\fR. If the file is opened for reading the offset +of the line which was just read or which is currently being read is returned. +If the file is opened for writing the offset of the next line to be written +or of the line currently being written is returned. In all cases the +offset points to the first character in a line, i.e., the first character +following the newline line delimiter character. +.ih +RETURN VALUE +If the operation is successful a magic integer describing the current file +offset is returned in \fIloffset\fR. If seeking is illegal on the device +associated with \fIchan\fR the return value is undefined. It is not an error +to call \fIznottx\fR on a file which does not permit seeks; if no seek is +ever performed no error has occurred. +.ih +NOTES +Depending on the host system, \fIloffset\fR might be a zero indexed byte +offset, the logical record number, the file block number and char offset +within the block packed into a long integer, or some other machine dependent +quantity. The high level code must do nothing with \fIloffset\fR but +request it with \fBznottx\fR and pass the value on to \fBzsektx\fR to perform +a seek. Seek offsets may be compared for equality but no other arithmetic +or logical operations are permissible. For example, if the offset of line A +is numerically less than the offset of line B, one \fIcannot\fR conclude that +line A is nearer the beginning of file than line B. +The only way to generate a seek offset for a text file (other than +to BOF or EOF) is to note the file position while reading or writing the file. +.ih +SEE ALSO +zsektx, zfiotx +.endhelp diff --git a/unix/os/doc/zopcpr.hlp b/unix/os/doc/zopcpr.hlp new file mode 100644 index 00000000..4addc55a --- /dev/null +++ b/unix/os/doc/zopcpr.hlp @@ -0,0 +1,33 @@ +.help zopcpr May84 "System Interface" +.ih +NAME +zopcpr -- open a connected subprocess +.ih +SYNOPSIS +.nf +zopcpr (process_file, inchan, outchan, pid) + +packed char process_file[] # executable file +int inchan # input from child +int outchan # output to child +int pid # pid of child +.fi +.ih +DESCRIPTION +The executable file \fIprocess_file\fR is spawned as a child process and +connected to the parent via the IPC (inter-process communication) channels +\fIinchan\fR and \fIoutchan\fR. +.ih +RETURN VALUE +ERR is returned if the named subprocess cannot be connected. If the connection +succeeds the process-id of the child is returned in \fIpid\fR. +.ih +NOTES +Only the IPC driver may be used to read and write the IPC channels. +A process spawned with \fBzopcpr\fR must be closed with \fBzclcpr\fR. +On a multi-processor system the OSFN \fIprocess_file\fR may be used to +specify the processor on which the child process is to be spawned. +.ih +SEE ALSO +zclcpr, zintpr, zopdpr +.endhelp diff --git a/unix/os/doc/zopdir.hlp b/unix/os/doc/zopdir.hlp new file mode 100644 index 00000000..6df42214 --- /dev/null +++ b/unix/os/doc/zopdir.hlp @@ -0,0 +1,34 @@ +.help zopdir May84 "System Interface" +.ih +NAME +zopdir -- open a directory file +.ih +SYNOPSIS +.nf +zopdir (osfn, chan) + +packed char osfn[] # directory file name +int chan # channel assigned to file +.fi +.ih +DESCRIPTION +The named directory file is opened for sequential access in READ_ONLY mode. +.ih +RETURN VALUE +ERR is returned in \fIchan\fR if the named file does not exist, is not a +directory, or cannot be accessed. A positive nonzero magic integer is +returned if the operation is successful. +.ih +NOTES +A directory file is opened at the kernel level with \fBzopdir\fR, +is read with \fBzgfdir\fR, and is closed with \fBzcldir\fR. +A directory file is viewed by the high level code as a simple list of +OS filenames; a directory file is interfaced to FIO as a text file and +successive filenames are read by the high level code with \fBgetline\fR. +The text file driver for a directory file is machine independent and +serves only as an interface between FIO and the three directory access +primitives. +.ih +SEE ALSO +zgfdir, zcldir +.endhelp diff --git a/unix/os/doc/zopdpr.hlp b/unix/os/doc/zopdpr.hlp new file mode 100644 index 00000000..acf90a30 --- /dev/null +++ b/unix/os/doc/zopdpr.hlp @@ -0,0 +1,37 @@ +.help zopdpr May84 "System Interface" +.ih +NAME +zopdpr -- open a detached process +.ih +SYNOPSIS +.nf +zopdpr (process_name, bkgfile, jobcode) + +packed char process_name[] # executable file name +packed char bkgfile[] # job file +int jobcode # job number of bkg job +.fi +.ih +DESCRIPTION +A background job is queued for execution at some unspecifiable future time. +The process named by the executable file \fIprocess_name\fR will eventually +execute as a detached process, i.e., independently of the parent process. +When the process runs it will read the file \fIbkgfile\fR to determine what +to do. The format of the background file is application dependent. +Deletion of the background file indicates that the background job +has terminated. +.ih +RETURN VALUE +ERR is returned if the background job cannot be queued for some reason. +If the operation is successful \fIjobcode\fR contains the positive nonzero +magic integer assigned by the kernel or by the host system to the job. +.ih +NOTES +The background job may execute immediately or may be placed in a queue +and executed at some later time, depending on the implementation chosen +for a particular host system. The significance of \fIjobcode\fR is +machine dependent. +.ih +SEE ALSO +zcldpr, zopcpr +.endhelp diff --git a/unix/os/doc/zopnbf.hlp b/unix/os/doc/zopnbf.hlp new file mode 100644 index 00000000..11adc331 --- /dev/null +++ b/unix/os/doc/zopnbf.hlp @@ -0,0 +1,53 @@ +.help zopnbf May84 "System Interface" +.ih +NAME +zopnbf -- open a binary file +.ih +SYNOPSIS +.nf +zopnbf (osfn, mode, chan) + +packed char osfn[] # OS filename +int mode # access mode +int chan # OS channel assigned to file +.fi +.ih +DESCRIPTION +File \fIosfn\fR is opened with access mode \fImode\fR and connected to +channel \fIchan\fR for binary file i/o. The legal access modes for a +binary file are as follows: + +.nf + READ_ONLY 1 open existing file for reading + READ_WRITE 2 open existing file for both r&w + WRITE_ONLY 3 open existing file for writing + APPEND 4 open or create file for appending + NEW_FILE 5 create a new file for both r&w +.fi + +APPEND mode is the same as WRITE_ONLY for most devices, except that in APPEND +mode a new file will be created if none already exists. +.ih +RETURN VALUE +ERR is returned if the named file does not exist or cannot be created, +if insufficient permission is available for the access mode requested, +or if an unknown access mode is specified. If the operation is successful +the magic integer channel number assigned to the channel is returned +in \fIchan\fR (a nonnegative integer value). +.ih +NOTES +FIO will not call \fBzopnbf\fR to open a new file if a file with the same +name already exists. FIO will instead either delete the file (if file clobber +is enabled) or take an error action. + +The file access permissions (owner, group, world permissions) of a new file +are initialized by the kernel to either host system default values or to user +definable values when the file is created. +The technique by which this is done is machine dependent. +Many systems provide an automatic system default set of permissions, +e.g., read permission for everyone but write permission only +for the owner, but give the user the option of globally overriding the default. +.ih +SEE ALSO +zclsbf, zfiobf, zopntx +.endhelp diff --git a/unix/os/doc/zopntx.hlp b/unix/os/doc/zopntx.hlp new file mode 100644 index 00000000..b2f008e1 --- /dev/null +++ b/unix/os/doc/zopntx.hlp @@ -0,0 +1,55 @@ +.help zopntx May84 "System Interface" +.ih +NAME +zopntx -- open a text file +.ih +SYNOPSIS +.nf +zopntx (osfn, mode, chan) + +packed char osfn[] # OS filename +int mode # access mode +int chan # OS channel assigned to file +.fi +.ih +DESCRIPTION +The text file \fIosfn\fR is opened with access mode \fImode\fR and +assigned the channel \fIchan\fR. The legal access modes for text files +are as follows: + +.nf + READ_ONLY 1 open existing file for reading + READ_WRITE 2 ** NOT SUPPORTED FOR TEXT FILES ** + WRITE_ONLY 3 same as append mode + APPEND 4 open or create for appending + NEW_FILE 5 create for appending +.fi + +If a nonexistent text file is opened for appending the file is created, +i.e., appending to a nonexistent file is equivalent to mode NEW_FILE. +READ_WRITE mode is not supported for text files since text file i/o is +sequential. +.ih +RETURN VALUE +ERR is returned if the named file does not exist, cannot be opened with the +specified access mode, cannot be created, or if an illegal mode is specified. +If the operation is successful the nonegative magic channel number assigned +by the kernel to the file is returned in \fIchan\fR. +.ih +NOTES +FIO will not call \fBzopntx\fR to open a new file if a file with the same +name already exists. FIO will instead either delete the file (if file clobber +is enabled) or take an error action. FIO does not assume anything about the +file position at open time; \fBzsektx\fR is called shortly after \fBzopntx\fR +to position the file to either BOF or EOF depending on the access mode. + +The file access permissions (owner, group, world permissions) of a new file +are set by the kernel to either host system default values or to user definable +values when the file is created. The technique by which this is done is machine +dependent. Many systems provide an automatic system default set of +permissions, e.g., read permission for everyone but write permission only +for the owner, but give the user the option of globally overriding the default. +.ih +SEE ALSO +zclstx, zfiotx, zopnbf +.endhelp diff --git a/unix/os/doc/zoscmd.hlp b/unix/os/doc/zoscmd.hlp new file mode 100644 index 00000000..3526506d --- /dev/null +++ b/unix/os/doc/zoscmd.hlp @@ -0,0 +1,36 @@ +.help zoscmd May84 "System Interface" +.ih +NAME +zoscmd -- send a command to the host operating system +.ih +SYNOPSIS +.nf +zoscmd (cmd, stdout, stderr, status) + +packed char cmd[] # command for host JCL +packed char stdout[] # standard output filename +packed char stderr[] # standard error filename +int status # termination status +.fi +.ih +DESCRIPTION +The machine dependent command \fIcmd\fR is executed by the standard host +command interpreter. Control does not return until the host has finished +executing the command. If either of the filenames \fIstdout\fR or \fIstderr\fR +is nonnull the kernel will attempt to append the referenced output stream +to the named textfile, which will be created if necessary. +.ih +RETURN VALUE +ERR is returned if an error occurred during execution of the command. +OK is returned if the command was executed successfully. +.ih +NOTES +This primitive may not be available in all implementations and any program +which uses it is nonportable. +.ih +BUGS +The output spooling feature cannot be relied upon. +.ih +SEE ALSO +clio.clcmd +.endhelp diff --git a/unix/os/doc/zpanic.hlp b/unix/os/doc/zpanic.hlp new file mode 100644 index 00000000..cc8d3454 --- /dev/null +++ b/unix/os/doc/zpanic.hlp @@ -0,0 +1,32 @@ +.help zpanic May84 "System Interface" +.ih +NAME +zpanic -- terminate process execution unconditionally +.ih +SYNOPSIS +.nf +zpanic (errcode, errmsg) + +int errcode # exit status +packed char errmsg[] # error message +.fi +.ih +DESCRIPTION +The error message \fIerrmsg\fR is written to the process standard error +output and the process terminates, returning \fIerrcode\fR to the parent +process as the exit status. +.ih +RETURN VALUE +This procedure does not return. +.ih +NOTES +The process standard error output is not well-defined. The kernel implementor +may hook the process standard error stream to whatever device seems most +appropriate on the host system. If the process was spawned interactively +this will probably be the user terminal. If the process is running in a +batch queue a file might be a better choice. Do not confuse the process +standard error output with the pseudofile STDERR. +.ih +SEE ALSO +zclcpr, zcldpr +.endhelp diff --git a/unix/os/doc/zputtx.hlp b/unix/os/doc/zputtx.hlp new file mode 100644 index 00000000..d04df541 --- /dev/null +++ b/unix/os/doc/zputtx.hlp @@ -0,0 +1,59 @@ +.help zputtx May84 "System Interface" +.ih +NAME +zputtx -- put next line to a text file +.ih +SYNOPSIS +.nf +zputtx (chan, text, nchars, status) + +int chan # OS channel of file +char text[nchars] # text data to be output +int nchars # number of characters in buffer +int status +.fi +.ih +DESCRIPTION +Exactly \fInchars\fR chars are written from the SPP char array \fItext\fR +to the text file connected to channel \fIchan\fR. Output is normally a line +of text, i.e., a sequence of zero or more characters terminated by the +\fBnewline\fR character (normally linefeed), although there is no guarantee +that the newline delimiter will be present. If the newline delimiter is +present it must be the final character and it must be counted in \fInchars\fR. +A blank line is output by calling \fBzputtx\fR with a single newline character +in \fItext\fR and with \fInchars\fR equal to one. Only ASCII data may be +written to a text file, i.e., the value of a char must be constrained to the +range 0 to 127. Writing is permitted only at EOF. +.ih +RETURN VALUE +ERR is returned for a write error or for an illegal call. If the write is +successful the number of characters written (including the newline) is returned +in \fIstatus\fR. +.ih +NOTES +There is no fixed upper limit on the length of a line. In normal usage FIO +calls \fBzputtx\fR to write out the internal FIO fixed size line buffer +whenever it sees a newline in the output. If an applications program writes +a very long line, the line buffer in FIO will overflow and \fBzputtx\fR will +be called to write out the contents of the buffer without a newline terminator. +FIO will also write out a partial line when the output is explicitly flushed. +On input FIO uses the same fixed size line buffer, and several calls to +\fBzgettx\fR may be required to read a full line. + +If the host system does not use the ASCII character set \fBzputtx\fR will +convert characters from ASCII to the host character set upon output. +The full ASCII character set is permitted, i.e., control characters may be +embedded in the text. For efficiency reasons character data is not checked +to verify that it is in the range 0 to 127. If non-ASCII data is input the +results are unpredictable. +.ih +BUGS +Individual IRAF and host system utilities may place their own limits on the +maximum length of a line of text. The lower bound on the size of a line +of text in IRAF programs is globally defined by the parameter SZ_LINE in +\fBiraf.h\fR and may easily be adjusted by the system installer. A sysgen +of the entire system is required as SZ_LINE is used everywhere. +.ih +SEE ALSO +zfiotx, zgettx +.endhelp diff --git a/unix/os/doc/zraloc.hlp b/unix/os/doc/zraloc.hlp new file mode 100644 index 00000000..5b423295 --- /dev/null +++ b/unix/os/doc/zraloc.hlp @@ -0,0 +1,45 @@ +.help zraloc May84 "System Interface" +.ih +NAME +zraloc -- reallocate memory +.ih +SYNOPSIS +.nf +zraloc (buffer, nbytes, status) + +int buffer # address of buffer +int nbytes # size of buffer +int status +.fi +.ih +DESCRIPTION +The size of the previously allocated buffer pointed to by \fIbuffer\fR is +changed to \fInbytes\fR. The buffer pointer must be the SPP char address +returned by a previous call to \fBzmaloc\fR or \fBzraloc\fR. +If necessary the buffer will be moved and the buffer pointer \fIbuffer\fR +modified to point to the new buffer. If the buffer is moved the contents of +the buffer are preserved. +.ih +RETURN VALUE +XERR is returned if the buffer pointer is invalid or if the buffer cannot be +reallocated. XOK is returned if the operation is successful. +.ih +NOTES +The integer \fIbuffer\fR is a memory address in SPP char units with an +arbitrary zero point, i.e., the type of address returned by \fBzlocva\fR. +The high level code converts the buffer address into an offset into \fBMem\fR, +i.e., into an SPP pointer. + +.nf + char_pointer_into_Mem = buffer - zlocva(Memc) + 1 + Memc[char_pointer] = first char of buffer +.fi + +Since the buffer address is returned in char units the buffer must be aligned +to at least the size of a char; no greater degree of alignment is guaranteed +nor required. See the specifications of \fBzlocva\fR for additional information +about addresses and address arithmetic. +.ih +SEE ALSO +zmaloc, zmfree, zlocva +.endhelp diff --git a/unix/os/doc/zsektx.hlp b/unix/os/doc/zsektx.hlp new file mode 100644 index 00000000..ad9c0020 --- /dev/null +++ b/unix/os/doc/zsektx.hlp @@ -0,0 +1,43 @@ +.help zsektx May84 "System Interface" +.ih +NAME +zsektx -- seek on a text file +.ih +SYNOPSIS +.nf +zsektx (chan, loffset, status) + +int chan # OS channel of text file +long loffset # magic seek offset +int status +.fi +.ih +DESCRIPTION +Text files are normally accessed sequentially, but random access is possible +when reading if \fBzsektx\fR is used to adjust the file position. +The primitive \fBzsektx\fR may be used to set the file position to BOF, EOF, +or to the beginning of any line in the file provided the offset of the line +was determined in a prior call to \fBznottx\fR while reading or writing the +file. +.ih +RETURN VALUE +ERR is returned if there is something wrong with \fIchan\fR or if seeks are +illegal on the device and the seek is to a file position other than BOF or +EOF. If seeks are illegal on the device a request to seek to BOF or EOF is +ignored. OK is returned if the seek is successful. +.ih +NOTES +Depending on the host system, \fIloffset\fR might be a zero indexed byte +offset, the logical record number, the file block number and char offset +within the block packed into a long integer, or some other machine dependent +quantity. The high level code must do nothing with \fIloffset\fR but +request it with \fBznottx\fR and pass the value on to \fBzsektx\fR to perform +a seek. The only way to generate a seek offset for a text file (other than +to BOF or EOF) is to note the file position while reading or writing the file. + +A note followed by a seek while reading or writing a line (newline not yet +seen) rewinds the line. +.ih +SEE ALSO +znottx, zfiotx +.endhelp diff --git a/unix/os/doc/zsttbf.hlp b/unix/os/doc/zsttbf.hlp new file mode 100644 index 00000000..5cca693e --- /dev/null +++ b/unix/os/doc/zsttbf.hlp @@ -0,0 +1,53 @@ +.help zsttbf May84 "System Interface" +.ih +NAME +zsttbf -- get file status for a binary file +.ih +SYNOPSIS +.nf +zsttbf (chan, param, lvalue) + +int chan # OS channel assigned to file +int param # parameter to be returned +long lvalue # return value of parameter +.fi +.ih +DESCRIPTION +The \fBzsttbf\fR primitive is used to obtain file, device, and machine +dependent information for the binary file (and device) connected to the +channel \fIchan\fR. The integer argument \fIparam\fR selects the parameter +to be returned; a separate call is required to access each parameter. +.ls +.ls FSTT_BLKSIZE (=1) +If the file is a blocked file, the size of a device block in bytes. +A streaming file is indicated by a device block size of zero. +Variable size records may be read from or written to a streaming file. +A blocked file with a block size of one byte denotes a randomly addressable +file with no blocking restrictions. +.le +.ls FSTT_FILSIZE (=2) +The current file size in machine bytes. FIO uses this parameter when +appending to blocked binary files. The file size is undefined for streaming +files. FIO will ask for this parameter once when the file is opened, +and thereafter FIO will keep track of the file size internally. +.le +.ls FSTT_OPTBUFSIZE (=3) +The optimum, i.e. default, buffer size for a FIO file buffer for "regular" +i/o. Should be an integral multiple of the device block size. +FIO will create a larger or smaller buffer if advised that i/o is to be +abnormally sequential or random in nature. The optimum transfer size is +expected to be both device and machine dependent. +.le +.ls FSTT_MAXBUFSIZE (=4) +The maximum size of a FIO file buffer, i.e., the maximum permissible +transfer size. If there is no maximum value zero is returned. +.le +.le +.ih +RETURN VALUE +ERR is returned (coerced into a long integer) if \fIchan\fR or \fIparam\fR +is illegal. The legal \fIlvalues\fR are all nonnegative integer values. +.ih +SEE ALSO +zfiobf +.endhelp diff --git a/unix/os/doc/zstttx.hlp b/unix/os/doc/zstttx.hlp new file mode 100644 index 00000000..59ac6e5a --- /dev/null +++ b/unix/os/doc/zstttx.hlp @@ -0,0 +1,50 @@ +.help zstttx May84 "System Interface" +.ih +NAME +zstttx -- get file status for a text file +.ih +SYNOPSIS +.nf +zstttx (chan, param, lvalue) + +int chan # OS channel assigned to file +int param # magic code for parameter +long lvalue # return value of parameter +.fi +.ih +DESCRIPTION +The \fBzstttx\fR primitive is used to obtain file, device, and machine +dependent information for the text file (and device) connected to the +channel \fIchan\fR. The magic integer \fIparam\fR selects the parameter +to be returned; a separate call is required to access each parameter. +.ls +.ls FSTT_BLKSIZE (=1) +Not used for text files; return value is undefined (but must be >= 0). +.le +.ls FSTT_FILSIZE (=2) +The current file size in machine bytes, possibly including space for record +headers. This parameter is purely informative and must not be used to +direct the flow of control, since the current file size is not a well defined +quantity for a text file. +.le +.ls FSTT_OPTBUFSIZE (=3) +The optimum, i.e. default, buffer size for a FIO text file line buffer. +Normally the same as SZ_LINE. +.le +.ls FSTT_MAXBUFSIZE (=4) +The maximum buffer size for a FIO text file line buffer. +Normally the maximum record size for the output device. +If there is no maximum value zero is returned. +.le +.le +.ih +RETURN VALUE +ERR is returned (coerced into a long integer) if \fIchan\fR or \fIparam\fR +is illegal. The legal \fIlvalues\fR are all nonnegative integer values. +.ih +NOTES +The file size is meaningless if the file is a terminal. +.ih +SEE ALSO +zfiotx +.endhelp diff --git a/unix/os/doc/zsvjmp.hlp b/unix/os/doc/zsvjmp.hlp new file mode 100644 index 00000000..50e229f7 --- /dev/null +++ b/unix/os/doc/zsvjmp.hlp @@ -0,0 +1,65 @@ +.help zsvjmp,zdojmp May84 "System Interface" +.ih +NAME +zsvjmp, zdojmp -- non-local goto +.ih +SYNOPSIS +.nf +include + +zsvjmp (jumpbuf, status) # save context for jump +zdojmp (jumpbuf, status) # restore context and jump + +int jumpbuf[LEN_JUMPBUF] # context saved by \fBzsvjmp\fR +int status # code returned by \fBzsvjmp\fR +.fi +.ih +DESCRIPTION +These primitives are used principally to restart interpreters (e.g. the IRAF +Main and the CL) following an error abort. +When an error occurs deep in a procedure calling sequence and the interpreter +(a higher level procedure) must be restarted, the hardware stack or stacks +and registers must be restored to their earlier state. + +The \fBzdojmp\fR primitive restores the context of the procedure which +originally called \fBzsvjmp\fR, causing control to return from \fBzsvjmp\fR +as if it had just been called. The calling procedure must not itself have +returned in the interim. +.ih +RETURN VALUE +The integer code \fIstatus\fR is zero the first time \fBzsvjmp\fR returns, +i.e., when \fBzsvjmp\fR is called by the main procedure to initialize +\fIjumpbuf\fR. When \fBzdojmp\fR is subsequently called to "goto" the +main procedure it should be called with a nonzero \fIstatus\fR to tell +the main procedure that it has been reentered at the point immediately +following the call to \fBzsvjmp\fR. +.ih +NOTES +Only the hardware stack and registers are restored by \fBzdojmp\fR. +Buffers which have been allocated since the first call to \fBzsvjmp\fR +will still be allocated, newly posted exception handlers will still be +posted, and so on. It is up to the high level code to clean up following +error restart. +.ih +EXAMPLE +Procedure A, the main (highest level) procedure, calls \fBzsvjmp\fR to +save its context for a subsequent restart, then calls procedure B. +Procedure B calls procedure C which directly or indirectly calls +\fBzdojmp\fR. The \fIjumpbuf\fR storage area is global. + +.ks +.nf +A: call zsvjmp (jumpbuf, status) + 99 if (status == error_code) + we were called from C + call B + +B: call C + +C: call zdojmp (jumpbuf, error_code) [e.g., goto 99] +.fi +.ke +.ih +SEE ALSO +A discussion of the IRAF Main and error recovery. +.endhelp diff --git a/unix/os/doc/ztslee.hlp b/unix/os/doc/ztslee.hlp new file mode 100644 index 00000000..fc1e61bf --- /dev/null +++ b/unix/os/doc/ztslee.hlp @@ -0,0 +1,31 @@ +.help ztslee May84 "System Interface" +.ih +NAME +ztslee -- suspend process execution (sleep) +.ih +SYNOPSIS +.nf +ztslee (nseconds) + +int nseconds # number of seconds to sleep +.fi +.ih +DESCRIPTION +Process execution is suspended for \fInseconds\fR seconds. +If \fInseconds\fR is negative or zero control returns immediately. +.ih +RETURN VALUE +None. +.ih +NOTES +The maximum number of seconds that a process can be put to sleep is +given by the machine constant MAX_INT. +.ih +BUGS +There is currently no way to generate a delay of less than a second. +An applications program cannot reliably slice time that fine on a +multiuser timesharing operating system. +.ih +SEE ALSO +zgtime +.endhelp diff --git a/unix/os/doc/zxgmes.hlp b/unix/os/doc/zxgmes.hlp new file mode 100644 index 00000000..6d0fc2da --- /dev/null +++ b/unix/os/doc/zxgmes.hlp @@ -0,0 +1,35 @@ +.help zxgmes May84 "System Interface" +.ih +NAME +zxgmes -- get info on most recent exception +.ih +SYNOPSIS +.nf +zxgmes (os_exception, errmsg, maxch) + +int os_exception # machine dependent exception code +packed char errmsg[maxch] # machine dependent error message +.fi +.ih +DESCRIPTION +A description of the most recent hardware or software exception is returned. +The integer code \fIos_exception\fR is the machine dependent code for the +exception, and \fIerrmsg\fR is a specific, machine dependent string +describing the exception. A program which merely calls \fBzxgmes\fR +to fetch and print the error message can be informative without compromising +portability (e.g., the default exception handlers do this). +.ih +RETURN VALUE +OK is returned for \fIos_exception\fR if no exception has occurred since +process startup or since the last call to \fBzxgmes\fR. If \fBzxgmes\fR +is called repeatedly following a single exception all calls after the first +will return OK. +.ih +NOTES +Any program which uses machine dependent exception codes is machine dependent. +The usage should be parameterized and documented in one of the system config +files. +.ih +SEE ALSO +zxwhen, zintpr +.endhelp diff --git a/unix/os/doc/zxwhen.hlp b/unix/os/doc/zxwhen.hlp new file mode 100644 index 00000000..17310a56 --- /dev/null +++ b/unix/os/doc/zxwhen.hlp @@ -0,0 +1,70 @@ +.help zxwhen May84 "System Interface" +.ih +NAME +zxwhen -- post an exception handler +.ih +SYNOPSIS +.nf +include + +zxwhen (exception, new_handler, old_handler) + +int exception # virtual exception code +int new_handler # EPA of new handler +int old_handler # EPA of old handler +.fi +.ih +DESCRIPTION +The exception handler procedure \fInew_handler\fR is posted for the specified +virutal exception, i.e., \fInew_handler\fR will be called if the indicated +exception should occur. The integer value of \fInew_handler\fR must be either +the entry point address (EPA) of a procedure as returned by \fBzlocpr\fR, or the +integer constant X_IGNORE (zero), used to disable exceptions. The recognized +virtual exceptions, defined in , are as follows: + +.nf + X_ACV 501 # access violation + X_ARITH 502 # arithmetic error + X_INT 503 # keyboard interrupt + X_IPC 504 # write to IPC with no reader +.fi + +In general many host-specific exceptions may be mapped to a single virtual +exception. All host exceptions which are not caught internally by the kernel +are mapped to one of the four virtual exceptions. An exception handler +remains posted after it has been called. The user exception handler must +have the following calling sequence: + + user_handler (exception, next_handler) + +The kernel calls the user handler procedure with the integer code of the +virtual exception which actually occurred as the first argument; thus a +single handler may be posted to more than one exception. The user handler +may either directly or indirectly call \fBzdojmp\fR to initiate error +recovery, in which case the procedure does not return. If the handler +procedure returns, \fInext_handler\fR must be set either to X_IGNORE or to the +EPA of the next_handler, i.e., to the value of \fIold_handler\fR received +when the current handler was posted. If X_IGNORE is returned execution +will continue normally. If the EPA of another handler procedure is returned +that handler will receive control, hence a chain of handlers may be called +to handle an exception. +.ih +RETURN VALUE +A panic exit occurs if an unknown \fIexception\fR is specified. If the +operation is successful \fIold_handler\fR will contain either X_IGNORE or +the EPA of the previous handler. +.ih +NOTES +The IRAF Main posts a default exception handler to all four exceptions upon +process startup. The default handler allows arithmetic exceptions to be +caught by inline error handlers (i.e., \fBiferr\fR statements) in user code. +Access violations and interrupts may only be caught by posting an exception +handler. If an exception is not caught program execution is aborted, +error restart occurs, and any user procedures posted with \fBonerror\fR are +callled. See the System Interface reference manual and the SPP reference +manual for a more detailed discussion of exception and error handling in +the high level code. +.ih +SEE ALSO +zxgmes, zintpr, zfiopr, onerror, SPP \fBiferr\fR and \fBerror\fR statements +.endhelp diff --git a/unix/os/doc/zzclmt.hlp b/unix/os/doc/zzclmt.hlp new file mode 100644 index 00000000..d3eb69f7 --- /dev/null +++ b/unix/os/doc/zzclmt.hlp @@ -0,0 +1,47 @@ +.help zzclmt May84 "System Interface" +.ih +NAME +zzclmt -- close a magtape file +.ih +SYNOPSIS +.nf +zzclmt (chan, mode, nrecords, nfiles, status) + +int chan # OS channel of magtape file +int mode # access mode of file +int nrecords # number of records skipped +int nfiles # number of filemarks skipped +int status +.fi +.ih +DESCRIPTION +The magtape file associated with the channel \fIchan\fR is closed, i.e., the +magtape device is freed for use by another process and the channel is freed +for use with another file. Closing a magtape file does not free the magtape +device for use by another user; the drive must also be \fBdeallocated\fR +before it can be accessed by another user. If \fImode\fR is WRITE_ONLY an +end of tape (EOT) mark is written at the current position of the tape. +.ih +RETURN VALUE +ERR is returned in \fIstatus\fR if \fIchan\fR is invalid or if the tapemark +could not be written. +The number of file records skipped when the tape was closed in returned +in \fInfiles\fR. +The number of filemarks skipped when the tape was closed in returned +in \fInfiles\fR. +A negative value is returned if the tape was backspaced. +.ih +NOTES +If error recovery occurs while positioning the tape, i.e., during a call +to \fBzzopmt\fR, \fBzzclmt\fR will be called with \fImode\fR set to READ_ONLY. +Otherwise the mode given is that given when the tape was opened. + +If a magtape file is opened for writing and immediately closed without writing +anything a zero length file may be written, i.e., an EOT mark. If another +file is then appended the new file will be unreachable once the tape is +rewound. To avoid this problem the high level code writes a short record +containing the ASCII string "NULLFILE" before closing the tape. +.ih +SEE ALSO +zzopmt, zfiomt, system.deallocate +.endhelp diff --git a/unix/os/doc/zzopmt.hlp b/unix/os/doc/zzopmt.hlp new file mode 100644 index 00000000..d739a9a3 --- /dev/null +++ b/unix/os/doc/zzopmt.hlp @@ -0,0 +1,62 @@ +.help zzopmt May84 "System Interface" +.ih +NAME +zzopmt -- open a magtape file +.ih +SYNOPSIS +.nf +zzopmt (drive, density, mode, oldrec, oldfile, newfile, chan) + +int drive # logical drive number (1, 2,...) +int density # e.g. 0, 800, 1600, 6250 +int mode # access mode (RO or WO) +int oldrec # current record number within file +int oldfile # current file number on tape +int newfile # requested/actual new file number +int chan # OS channel assigned to file +.fi +.ih +DESCRIPTION +The magnetic tape on logical drive number \fIdrive\fR is opened positioned to +record 1 (the first record) of file number \fInewfile\fR. The logical drive +numbers 1 through N, where N is the number of logical tape drives on the host +system, are associated with the user interface logical drive names "mta", +"mtb", etc. by the high level code. +The current position of the tape at open time is +given by the arguments \fIoldrec\fR and \fIoldfile\fR. When the tape is +rewound it is positioned to record 1 of file 1. The file number \fInewfile\fR +is either the number of the desired file on the tape (newfile >= 1) or EOT +(newfile <= 0). There is no way to position beyond EOT. The \fIdensity\fR +is a magic number of significance only to the user and to the kernel. +The tape is opened with a device dependent default density if \fIdensity\fR +is zero. The legal access modes for a magtape file are READ_ONLY and +WRITE_ONLY. +.ih +RETURN VALUE +ERR is returned in \fIchan\fR if there is no such drive, if the drive +does not support the requested density, if the tape cannot be positioned, +or if the drive cannot be physically opened. It is not an error if the +file number is out of range; the actual number of the file to which the tape +was positioned is returned in \fInewfile\fR. If the tape contains N files +and \fBzzopmt\fR is called to open the tape positioned to EOT, \fInewfile\fR +will have the value N+1 when the procedure exits. +.ih +NOTES +The high level procedure \fBmtopen\fR verifies that the drive has been +allocated and that the drive is not already open before calling \fBzzopmt\fR. +The \fIchan\fR argument should be set when the drive is physically opened, +rather than upon exit from \fBzzopmt\fR, in case an exception occurs while +the tape is being positioned (the high level error recovery code must have +the channel number to close the device). If the drive is to be opened +WRITE_ONLY the kernel should open the drive READ_ONLY to position to the +desired file, then close the drive and reopen for writing. This prevents +truncation of the tape from writing a tape mark if error recovery occurs while +the tape is being positioned (error recovery will call \fBzzclmt\fR). +.ih +BUGS +The tape may runaway if the density is incorrectly specified or if a blank +tape is opened for reading or appending. +.ih +SEE ALSO +zfiomt, mtopen, system.allocate, system.devstatus +.endhelp diff --git a/unix/os/doc/zzrdmt.hlp b/unix/os/doc/zzrdmt.hlp new file mode 100644 index 00000000..aea7cf74 --- /dev/null +++ b/unix/os/doc/zzrdmt.hlp @@ -0,0 +1,37 @@ +.help zzrdmt May84 "System Interface" +.ih +NAME +zzrdmt -- asynchronous read from a magtape file +.ih +SYNOPSIS +.nf +zzrdmt (chan, buf, maxbytes) + +int chan # OS channel of magtape file +char buf[maxbytes] # output buffer to receive data +int maxbytes # capacity of buffer +.fi +.ih +DESCRIPTION +Initiate a read of at most \fImaxbytes\fR bytes from channel \fIchan\fR into +the buffer \fIbuf\fR. If the physical file block is larger than \fImaxbytes\fR +bytes the additional data will be discarded. Each call to \fBzzrdmt\fR reads +one tape block. Successive tape blocks may vary in size. +.ih +RETURN VALUE +The wait primitive \fBzzwtmt\fR must be called after every asynchronous read +to get the transfer status. ERR is returned if a read error occurs or if the +channel number is illegal. If the read operation is successful the actual +number of bytes read is returned; zero is returned for a read at EOF. +.ih +NOTES +The transfer is NOT guaranteed to be asynchronous and the calling program +must not assume that \fBzzrdmt\fR will return immediately. +The \fBzzwtmt\fR primitive must be called and the status checked before +issuing another i/o request to the channel. Only a single request may be +pending on a channel at a time. A request to read zero bytes is considered +to be an error to avoid confusion with a read at EOF. +.ih +SEE ALSO +zzopmt, zzwtmt, zfiomt +.endhelp diff --git a/unix/os/doc/zzrwmt.hlp b/unix/os/doc/zzrwmt.hlp new file mode 100644 index 00000000..b771b506 --- /dev/null +++ b/unix/os/doc/zzrwmt.hlp @@ -0,0 +1,31 @@ +.help zzrwmt May84 "System Interface" +.ih +NAME +zzrwmt -- rewind magtape +.ih +SYNOPSIS +.nf +zzrwmt (chan, status) + +int chan # OS channel of magtape +int status +.fi +.ih +DESCRIPTION +A rewind of the magnetic tape opened on channel \fIchan\fR is initiated. +.ih +RETURN VALUE +ERR is returned if the tape is offline or if \fIchan\fR is illegal. +OK is returned if the operation is successful. +.ih +NOTES +The rewind is not guaranteed to be asynchronous. There is no wait primitive +for the rewind operation; it is assumed that the host driver or the kernel +will automatically suspend any further tape motion commands issued before +the rewind is completed. If the host system does not have the ability to +asynchronously rewind a magtape then \fBzzrwmt\fR is equivalent to a call +to \fBzzopmt\fR to open file 1 on a tape. +.ih +SEE ALSO +zzopmt, zfiomt +.endhelp diff --git a/unix/os/doc/zzwrmt.hlp b/unix/os/doc/zzwrmt.hlp new file mode 100644 index 00000000..1b4b01c5 --- /dev/null +++ b/unix/os/doc/zzwrmt.hlp @@ -0,0 +1,36 @@ +.help zzwrmt May84 "System Interface" +.ih +NAME +zzwrmt -- asynchronous write to a magtape file +.ih +SYNOPSIS +.nf +zzwrmt (chan, buf, nbytes) + +int chan # OS channel of magtape file +char buf[nbytes] # buffer containing the data +int nbytes # number of bytes to be written +.fi +.ih +DESCRIPTION +Initiate a write of exactly \fInbytes\fR bytes from the buffer \fIbuf\fR to +the magtape channel \fIchan\fR. Each call to \fBzzwrmt\fR writes one tape +block. Successive tape blocks may vary in size. A request to write zero +bytes is ignored. +.ih +RETURN VALUE +The wait primitive \fBzzwtmt\fR must be called after every asynchronous write +to get the transfer status. ERR is returned if a write error occurs or if the +channel number is illegal. If the write operation is successful the actual +number of bytes written is returned. +.ih +NOTES +The transfer is NOT guaranteed to be asynchronous and the calling program +must not assume that \fBzzwrmt\fR will return immediately. +The \fBzzwtmt\fR primitive must be called and the status checked before +issuing another i/o request to the channel. Only a single request may be +pending on a channel at a time. +.ih +SEE ALSO +zzopmt, zzwtmt, zfiomt +.endhelp diff --git a/unix/os/doc/zzwtmt.hlp b/unix/os/doc/zzwtmt.hlp new file mode 100644 index 00000000..3a975055 --- /dev/null +++ b/unix/os/doc/zzwtmt.hlp @@ -0,0 +1,41 @@ +.help zzwtmt May84 "System Interface" +.ih +NAME +zzwtmt -- wait for i/o on a magtape file +.ih +SYNOPSIS +.nf +zzwtmt (chan, nrecords, nfiles, status) + +int chan # OS channel of magtape file +int nrecords # nrecords skipped +int nfiles # nfiles skipped +int status +.fi +.ih +DESCRIPTION +If a transfer is in progress on the channel \fIchan\fR process execution +is suspended until the transfer completes. +.ih +RETURN VALUE +ERR is returned in \fIstatus\fR if a read or write error occurred in the +last i/o transfer to the magtape device. +The number of tape records (blocks) and/or filemarks skipped in the last +read or write operation is returned in \fInrecords\fR and \fInfiles\fR. +The number of bytes read or written is returned in \fIstatus\fR. +In an ordinary read or write operation \fInrecords\fR will be positive one, +\fInfiles\fR will be zero, and \fIstatus\fR will be a positive number. +An attempt to read at EOF will result in a \fIstatus\fR of zero (zero bytes +were read). Repeated calls to \fBzzwtmt\fR will continue to return the +same values. +.ih +NOTES +The \fInfiles\fR parameter will not necessarily be set to 1 when a filemark +is read, hence it cannot be used to test for EOF. Some systems will leave +the tape positioned to just before a filemark when a filemark is encountered +in a read operation, while others will leave the tape positioned to just +after the filemark. +.ih +SEE ALSO +zzrdmt, zzwrmt, zfiomt +.endhelp diff --git a/unix/os/getproc.c b/unix/os/getproc.c new file mode 100644 index 00000000..fc1c5921 --- /dev/null +++ b/unix/os/getproc.c @@ -0,0 +1,134 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#ifdef SUNOS + +#include +#include +#include +#include +#include +#include + +#define SYMBOLS "/vmunix" +#define KMEM "/dev/kmem" + + +/* UID_EXECUTING -- Search the process table to determine if the given UID + * belongs to any currently running processes. + */ +int +uid_executing (int uid) +{ + register struct proc *pt; + register int found, kmem, i; + struct proc *get_processtable(); + int nproc; + + if ((kmem = open (KMEM, 0)) == -1) { + fprintf (stderr, "Cannot open kernel memory\n"); + return (-1); + } else if ((pt = get_processtable (kmem, &nproc)) == NULL) + return (-1); + + for (found=0, i=0; i < nproc; i++) + if ((&pt[i])->p_stat) + if ((&pt[i])->p_uid == uid) { + found++; + break; + } + + free ((char *)pt); + close (kmem); + + return (found); +} + + +/* GET_PROCESSTABLE -- Take a snapshot of the current kernel process table. + */ +struct proc * +get_processtable ( + int kmem, /* fd of kernel memory file */ + int *o_nproc /* number of processes in output table */ +) +{ + char *symbols = SYMBOLS; + struct proc *pt = NULL; + struct nlist nl[3]; + int nproc, nb; + long proc; + + /* Check that the kernel symbol table file exists. */ + if (access (symbols, R_OK) < 0) { + fprintf (stderr, "Cannot open symbol file %s\n", symbols); + return (NULL); + } + + /* Get addresses of symbols '_proc' and '_nproc'. */ + nl[0].n_name = "_proc"; + nl[1].n_name = "_nproc"; + nl[2].n_name = NULL; + nlist (symbols, nl); + if (nl[0].n_value == -1) { + fprintf (stderr, "Cannot read symbol file %s\n", symbols); + return (NULL); + } + + /* Get values of these symbols from the kernel. */ + lseek (kmem, (long)nl[0].n_value, 0); + if (read (kmem, &proc, sizeof(proc)) <= 0) { +kerr: fprintf (stderr, "Cannot read kernel memory\n"); + return (NULL); + } + lseek (kmem, (long)nl[1].n_value, 0); + if (read (kmem, &nproc, sizeof(nproc)) <= 0) + goto kerr; + + /* Read the kernel process table. */ + if (nproc > 0) { + nb = nproc * sizeof(struct proc); + pt = (struct proc *) malloc (nb); + lseek (kmem, proc, 0); + if (read (kmem, pt, nb) < nb) + goto kerr; + } + + *o_nproc = nproc; + return (pt); +} + +#else /* Solaris */ + +#include +#include +#include +#include + + +/* UID_EXECUTING -- Search the process table to determine if the given UID + * belongs to any currently running processes. This is straightfoward for + * Solaris since each process has a file entry in /proc. + */ +int +uid_executing (int uid) +{ + register struct dirent *direntp; + register DIR *dirp; + char fname[256]; + struct stat st; + + dirp = opendir ("/proc"); + while ((direntp = readdir(dirp)) != NULL) { + sprintf (fname, "/proc/%s", direntp->d_name); + if (stat (fname, &st)) + return (0); + else if (st.st_uid == uid) + return (1); + } + (void) closedir (dirp); + + return (0); +} + +#endif diff --git a/unix/os/gmttolst.c b/unix/os/gmttolst.c new file mode 100644 index 00000000..cfe7c0a4 --- /dev/null +++ b/unix/os/gmttolst.c @@ -0,0 +1,73 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#ifdef SYSV +#include +#else +#include +#include +#endif + +#ifdef MACOSX +#include +#endif + +#define SECONDS_1970_TO_1980 315532800L +static long get_timezone(); + +/* GMT_TO_LST -- Convert gmt to local standard time, epoch 1980. + */ +time_t +gmt_to_lst (gmt) +time_t gmt; +{ + struct tm *localtime(); + time_t time_var; + long gmtl; + + /* Subtract seconds westward from GMT */ + time_var = gmt - get_timezone(); + + /* Correct for daylight savings time, if in effect */ + gmtl = (long)gmt; + +#ifndef MACOSX + /* Mac systems already include the DST offset in the GMT offset */ + if (localtime(&gmtl)->tm_isdst) + time_var += 60L * 60L; +#endif + + return (time_var - SECONDS_1970_TO_1980); +} + + +/* _TIMEZONE -- Get the local timezone, measured in seconds westward + * from Greenwich, ignoring daylight savings time if in effect. + */ +static long +get_timezone() +{ +#ifdef CYGWIN + extern long _timezone; + tzset(); + return (_timezone); +#else +#ifdef SYSV + extern long timezone; + tzset(); + return (timezone); +#else +#ifdef MACOSX + struct tm *tm; + time_t clock = time(NULL); + tm = localtime (&clock); + return (-(tm->tm_gmtoff)); +#else + struct timeb time_info; + ftime (&time_info); + return (time_info.timezone * 60); +#endif +#endif +#endif +} diff --git a/unix/os/irafpath.c b/unix/os/irafpath.c new file mode 100644 index 00000000..d498be4b --- /dev/null +++ b/unix/os/irafpath.c @@ -0,0 +1,165 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include + +#define import_spp +#define import_kernel +#define import_knames +#include + +#define SZ_ULIBSTR 512 +#define ULIB "IRAFULIB" + +extern char *getenv(); + + +/* IRAFPATH -- Determine the pathname of the given IRAF library file. If the + * file is found the full pathname is returned, else the given filename is + * returned. A list of user directories is first searched if defined, followed + * by the IRAF system directories, allowing users to have custom versions of + * the system files, e.g., for testing purposes. + */ +char * +irafpath (fname) +char *fname; /* simple filename, no dirs */ +{ + static char pathname[SZ_PATHNAME+1]; + PKCHAR ulibs[SZ_ULIBSTR+1]; + PKCHAR hostdir[SZ_LINE+1]; + PKCHAR irafdir[SZ_LINE+1]; + PKCHAR ldir[SZ_FNAME+1]; + XINT sz_ulibs=SZ_ULIBSTR; + XINT x_maxch=SZ_LINE, x_status; + char *ip, *op, *irafarch; + + extern int ZGTENV(); + + + /* Search any user libraries first. */ + strcpy ((char *)ldir, ULIB); + (void) ZGTENV (ldir, ulibs, &sz_ulibs, &x_status); + if (x_status > 0) + for (ip=(char *)ulibs; *ip; ) { + /* Get next user directory pathname. */ + while (isspace (*ip)) + ip++; + if (!*ip) + break; + for (op=pathname; *ip && !isspace(*ip); ) + *op++ = *ip++; + if (*(op-1) != '/') + *op++ = '/'; + *op = '\0'; + + strcat (pathname, fname); + if (access (pathname, 0) == 0) + return (pathname); + } + + /* Get the root pathnames. */ + strcpy ((char *)ldir, "host"); + ZGTENV (ldir, hostdir, &x_maxch, &x_status); + if (x_status <= 0) + return (fname); + strcpy ((char *)ldir, "iraf"); + ZGTENV (ldir, irafdir, &x_maxch, &x_status); + if (x_status <= 0) + return (fname); + + /* Look first in HBIN. + */ + strcpy (pathname, (char *)hostdir); + strcat (pathname, "bin."); + +#ifdef LINUXPPC + strcat (pathname, "linuxppc"); +#else +#ifdef CYGWIN + strcat (pathname, "cygwin"); +#else +#ifdef LINUX64 + strcat (pathname, "linux64"); +#else +#ifdef REDHAT + strcat (pathname, "redhat"); +#else +#ifdef LINUX + strcat (pathname, "linux"); +#else +#ifdef BSD + strcat (pathname, "freebsd"); +#else +#ifdef IPAD + strcat (pathname, "ipad"); +#else +#ifdef MACOSX + /* Setup for cross-compilation, default to 'macintel'. + */ + if ((irafarch = getenv("IRAFARCH"))) { + if (strcmp (irafarch, "macosx") == 0) + strcat (pathname, "macosx"); + else if (strcmp (irafarch, "macintel") == 0) + strcat (pathname, "macintel"); + else + strcat (pathname, "macosx"); + } else + strcat (pathname, "macintel"); +#else +#ifdef SOLARIS +#ifdef X86 + strcat (pathname, "sunos"); +#else + strcat (pathname, "ssol"); +#endif +#else +#ifdef sparc + strcat (pathname, "sparc"); +#else +#endif +#endif +#endif +#endif +#endif +#endif +#endif +#endif +#endif +#endif + + strcat (pathname, "/"); + strcat (pathname, fname); + if (access (pathname, 0) == 0) + return (pathname); + + /* Try HLIB */ + strcpy (pathname, (char *)hostdir); + strcat (pathname, "hlib/"); + strcat (pathname, fname); + if (access (pathname, 0) == 0) + return (pathname); + + /* Try BIN - use IRAFARCH if defined. */ + if ( (irafarch = getenv("IRAFARCH")) ) { + strcpy (pathname, (char *)irafdir); + strcat (pathname, "bin."); + strcat (pathname, irafarch); + strcat (pathname, "/"); + } else { + strcpy (pathname, (char *)irafdir); + strcat (pathname, "bin/"); + } + strcat (pathname, fname); + if (access (pathname, 0) == 0) + return (pathname); + + /* Try LIB */ + strcpy (pathname, (char *)irafdir); + strcat (pathname, "lib/"); + strcat (pathname, fname); + if (access (pathname, 0) == 0) + return (pathname); + + return (fname); +} diff --git a/unix/os/mkpkg b/unix/os/mkpkg new file mode 100644 index 00000000..379da65f --- /dev/null +++ b/unix/os/mkpkg @@ -0,0 +1,98 @@ +# Make the 4.2BSD UNIX IRAF kernel. All modules are also dependent on the +# header file . + +$checkout libos.a hlib$ +$update libos.a +$checkin libos.a hlib$ +$exit + +alloc: + !cc -O alloc.c getproc.c -o alloc.e; chmod 4755 alloc.e;\ + mv -f alloc.e ../hlib + ; + +libos.a: + $set XFLAGS = "-cd $(HSI_XF)" + + $ifdef (DEBUG) + $iffile (as$zsvjmp_p.s) + as$zsvjmp_p.s + $else + as$zsvjmp.s + $endif + $else + as$zsvjmp.s + $endif + + #"as$enbint.s" + + # Do not put zmain.o in the library if it is linked explicitly as a .o + # on the host machine. Having it in the library prevents use of the + # libos library in Fortran on a UNIX system as the linker will use the + # iraf zmain (C "main") rather than the Fortran one. + + $ifeq (USE_LIBMAIN, no) + zmain.c + $endif + + irafpath.c + gmttolst.c + prwait.c + zalloc.c + zawset.c + zdojmp.c + zcall.c + zfunc.c + zfacss.c + zfaloc.c + zfchdr.c + zfdele.c + zfgcwd.c + zfinfo.c + zfiobf.c + zfioks.c + zfiolp.c + zfiond.c + zfiomt.c + zfiopl.c + zfiopr.c + zfiosf.c + zfiotx.c + zfioty.c + zfmkcp.c + zfmkdr.c + zfnbrk.c + zfpath.c + zfpoll.c + zfprot.c + zfrnam.c + zfrmdr.c + zfsubd.c + zfutim.c + zfxdir.c + zgcmdl.c + zghost.c + zglobl.c + zgmtco.c + zgtenv.c + zgtime.c + zgtpid.c + zintpr.c + zlocpr.c + zlocva.c + zmaloc.c + zmfree.c + zopdir.c + zopdpr.c + zoscmd.c + zpanic.c + zraloc.c + zshlib.c + zwmsec.c + zxwhen.c + zzepro.c + zzexit.c + zzpstr.c + zzsetk.c + zzstrt.c + ; diff --git a/unix/os/mkpkg.sh b/unix/os/mkpkg.sh new file mode 100644 index 00000000..5507468e --- /dev/null +++ b/unix/os/mkpkg.sh @@ -0,0 +1,42 @@ +# Bootstrap the LIBOS.A library. + +echo "--------------------- OS ----------------------" + + +$CC -c $HSI_CF -Wall alloc.c getproc.c +$CC $HSI_LF -Wall alloc.o getproc.o $HSI_OSLIBS -o alloc.e +chmod 4755 alloc.e +mv -f alloc.e ../hlib +rm -f alloc.o + + +if test "$IRAFARCH" != "macosx"; then + for i in zsvjmp ;\ + do $CC -c $HSI_CF -Wall ../as/$i.s -o $i.o ;\ + done +fi + + +for i in gmttolst.c irafpath.c prwait.c z*.c ;\ + do $CC -c $HSI_CF -Wall $i ;\ +done + +#ar rv libos.a *.o; ar dv libos.a zmain.o; rm *.o + +if [ "$IRAFARCH" = "macosx" ]; then +## $CC -c -O -DMACOSX -w -Wunused -arch ppc ../as/zsvjmp_ppc.s -o zsvjmp.o ;\ +## libtool -a -T -o libos.a zsvjmp.o +## rm -f zsvjmp.o + $CC -c -O -DMACOSX -w -Wunused -m32 -arch i386 ../as/zsvjmp_i386.s -o zsvjmp.o ;\ + ar r libos.a *.o; + ranlib libos.a + rm -f zsvjmp.o zmain.o + +else + rm -f zmain.o + ar r libos.a *.o; + ranlib libos.a +fi + +rm *.o +mv -f libos.a ../bin diff --git a/unix/os/mkproto b/unix/os/mkproto new file mode 100755 index 00000000..4a59b252 --- /dev/null +++ b/unix/os/mkproto @@ -0,0 +1,5 @@ +#!/bin/sh + +flags="-DLINUX -DREDHAT -DPOSIX -DSYSV -DLINUX64" + +cproto -e $flags *.c > ../hlib/libc/kproto64.h diff --git a/unix/os/net/README b/unix/os/net/README new file mode 100644 index 00000000..af93d174 --- /dev/null +++ b/unix/os/net/README @@ -0,0 +1,90 @@ +NETwork interface. 08Oct85 dct +------------------------------------ + +This directory contains the network interface software required to support the +ZFIOKS FIO driver (for the kernel interface) in a TCP/IP environment. The only +facilities required are those already provided by the IRAF kernel (i.e., to +read the host name table, a text file), plus the standard TCP network functions +provided by any system that supports TCP/IP. The interface is self contained, +requiring only the host TCP/IP facilities and the file "uhosts" in iraf$dev, +used to map host names to network addresses (see gethostbyname). The code +supplied here is coded for Berkeley UNIX and works fine, but a much simpler +Berkeley UNIX dependent version of ZFIOKS is what is actually used on a +Berkeley host. + +The networking interface is not required to run IRAF and the contents of this +directory may be ignored if the IRAF system is to be configured without +networking. On a system configured without networking the entry points of the +ZFIOKS driver must be present but may be stubbed out. Additional information +on configuration details is given in the discussion of the kernel interface, +e.g., in sys$ki. + + +STRUCTURE + + The structure of the network interface software is as follows: + + + ZFIOKS FIO device driver for the kernel server (in ..) + | + REXEC remote execution of a shell command + | + TCP_xxx encapsulation of TCP interface + | + (host TCP/IP) host networking facilities + + +This software is machine dependent but is designed to be reusable, i.e., the +machine dependence has been isolated into simple procedures and definitions +whenever possible. On a Berkeley UNIX system the TCP procedures map directly +into the system services of 4.2 Berkeley UNIX (and no doubt later versions as +well). On a VMS system running EUNICE the TCP procedures map easily into +QIOW type device driver calls; EUNICE implements the TCP facilties in the +network device driver. Similar mappings should be possible on other systems +with TCP/IP support. + + +TCP INTERFACE + + The TCP interface package consists of the following procedures. On a +Berkeley UNIX system these TCP functions map directly into calls to the UNIX +system services. + + + tcp_gethostbyname get internet code for a host by name + tcp_getsockname get socket name + + tcp_socket create and bind a socket (client or server) + tcp_connect connect to a socket (client) + tcp_listen listen for connections on a socket (server) + tcp_accept accept a connection (server) + tcp_read read from a socket (synchronous) + tcp_write write to a socket (synchronous) + tcp_close close a socket (client or server) + + +The usual sequence of calls used by a client process to connect to and +communicate with a server process is the following. + + + gethostbyname;involves scan of hostname table + + make a socket + connect to the socket + (connect returns when the server has accepted the connection) + read & write data packets + (etc.) + close the socket + + +A server does much the same thing, except that the server will listen() for +connections by client processes, accept() a connection when one occurs, and +then begin exchanging packets with the client. + +------------------------------------ +NOTE -- This directory contains software which is adapted from the Berkeley UNIX +networking software, hence a UNIX source license is required to use this +software. Nonetheless, about 90% of the source herein is new; at some point +the remainder (only 100-200 lines) should be rewritten from scratch to eliminate +the proprietary restrictions. This was not done initially since the network +interface is not expected to be included in the standard distribution. diff --git a/unix/os/net/accept.c b/unix/os/net/accept.c new file mode 100644 index 00000000..578c1eba --- /dev/null +++ b/unix/os/net/accept.c @@ -0,0 +1,26 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" + +extern int errno; +extern int tcperrno; + +/* TCP_ACCEPT -- Accept a connection on a socket. Accept extracts the first + * connection from the queue of pending connections (set up with LISTEN), + * creates a new socket with the same properties as S and allocates a new + * file descriptor NS for the socket. + */ +u_sock +tcp_accept (s, addr, addrlen) +u_sock s; /* the socket */ +struct sockaddr *addr; /* endpoint of communications */ +int *addrlen; /* sizeof (addr) */ +{ + u_sock ns; + + /* MACHDEP */ + ns = accept (s, addr, addrlen); + tcperrno = errno; + return (ns); +} diff --git a/unix/os/net/connect.c b/unix/os/net/connect.c new file mode 100644 index 00000000..aeb2b959 --- /dev/null +++ b/unix/os/net/connect.c @@ -0,0 +1,27 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" + +extern int errno; +int tcperrno; + +/* TCP_CONNECT -- Initiate a connection on a socket. Returns when the server + * accepts the connection and a full duplex connection has been established. + * Zero is returned if the connection succeeds; -1 is returned if the connection + * fails. The sockaddr argument is necessary because a socket may be used to + * talk to multiple endpoints. + */ +tcp_connect (s, name, namelen) +u_sock s; /* the socket */ +struct sockaddr *name; /* endpoint of communications */ +int namelen; /* sizeof(name) */ +{ + int status; +eprintf("connect\n"); + + /* MACHDEP */ + status = connect (s, name, namelen); + tcperrno = errno; + return (status); +} diff --git a/unix/os/net/ctype.h b/unix/os/net/ctype.h new file mode 100644 index 00000000..3a1569c3 --- /dev/null +++ b/unix/os/net/ctype.h @@ -0,0 +1,4 @@ +#define isdigit(c) ((c)>='0'&&(c)<='9') +#define isxdigit(c) (isdigit(c)||(c)>='a'&&(c)<='f'||(c)>='A'&&(c)<='F') +#define islower(c) ((c)>='a'&&(c)<='z') +#define isspace(c) ((c)==' '||(c)=='\t'||(c)=='\n') diff --git a/unix/os/net/eprintf.c b/unix/os/net/eprintf.c new file mode 100644 index 00000000..4f6bbf06 --- /dev/null +++ b/unix/os/net/eprintf.c @@ -0,0 +1,15 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include + +/* EPRINTF -- Formatted print to the standard error output. + */ +/* VARARGS */ +eprintf (format, argp) +char *format; /* format specification */ +int **argp; /* pointer to arg list */ +{ + _doprnt (format, &argp, stderr); + fflush (stderr); +} diff --git a/unix/os/net/ghostbynm.c b/unix/os/net/ghostbynm.c new file mode 100644 index 00000000..42c9fb4a --- /dev/null +++ b/unix/os/net/ghostbynm.c @@ -0,0 +1,37 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include "netdb.h" + +#define import_kernel +#define import_knames +#define import_spp +#include + + +/* TCP_GETHOSTBYNAME -- Scan the host name table to get the internet address + * of the named host. + */ +struct hostent * +tcp_gethostbyname (name) +register char *name; +{ + register struct hostent *p; + register char **cp; + struct hostent *tcp_ghostent(); + +eprintf("gethostbyname %s\n", name); + tcp_ophnt(); + + while (p = tcp_ghostent()) { + if (strcmp (p->h_name, name) == 0) + break; + for (cp = p->h_aliases; *cp != 0; cp++) + if (strcmp (*cp, name) == 0) + goto found; + } +found: + tcp_clhnt(); + return (p); +} diff --git a/unix/os/net/ghostent.c b/unix/os/net/ghostent.c new file mode 100644 index 00000000..484e2640 --- /dev/null +++ b/unix/os/net/ghostent.c @@ -0,0 +1,137 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include "types.h" +#include "netdb.h" +#include "socket.h" + +#define import_kernel +#define import_knames +#define import_spp +#include + +#define MAXALIASES 35 +#define MAXADDRSIZE 14 +#define LINSIZ 80 + +static int hostf = NULL; +static char line[LINSIZ+1]; +static char hostaddr[MAXADDRSIZE]; +static struct hostent host; +static char *host_aliases[MAXALIASES]; +static char *tcp_locate(); + + +/* TCP_GHOSTENT -- Return the next entry (line) in the host name table + * decoded into a hostent structure. + * + * The format of an entry in the host name table (e.g., /etc/hosts on a UNIX + * system) is as follows: + * + * ddd.ddd alias1 alias2 ... aliasN + */ +struct hostent * +tcp_ghostent() +{ + register char *cp, **q; + u_long tcp_inetaddr(); + char *p, *tcp_hostdb(); + char *ku_fgets(); + + if (hostf == NULL && (hostf = ku_fopen (tcp_hostdb(), "r" )) == NULL) + return (NULL); + +again: + if ((p = ku_fgets (line, LINSIZ, hostf)) == NULL) + return (NULL); +eprintf("..%s", line); + + if (*p == '#') + goto again; + cp = tcp_locate (p, "#\n"); + if (cp == NULL) + goto again; + + *cp = '\0'; + cp = tcp_locate (p, " \t"); + if (cp == NULL) + goto again; + *cp++ = '\0'; + + /* THIS STUFF IS INTERNET SPECIFIC. + */ + host.h_addr = hostaddr; + *((u_long *)host.h_addr) = tcp_inetaddr (p); + host.h_length = sizeof (u_long); + host.h_addrtype = AF_INET; + + while (*cp == ' ' || *cp == '\t') + cp++; + host.h_name = cp; + + q = host.h_aliases = host_aliases; + cp = tcp_locate (cp, " \t"); + if (cp != NULL) + *cp++ = '\0'; + + while (cp && *cp) { + if (*cp == ' ' || *cp == '\t') { + cp++; + continue; + } + if (q < &host_aliases[MAXALIASES - 1]) + *q++ = cp; + cp = tcp_locate (cp, " \t"); + if (cp != NULL) + *cp++ = '\0'; + } + + *q = NULL; + + return (&host); +} + + +/* TCP_OPHNT -- Open the host name table, a text file. + */ +tcp_ophnt() +{ + char *tcp_hostdb(); + +eprintf ("ophnt %s\n", tcp_hostdb); + if (hostf == NULL) + hostf = ku_fopen (tcp_hostdb(), "r"); +} + + +/* TCP_CLHNT -- Close the host name table file. + */ +tcp_clhnt() +{ + if (hostf) { + ku_fclose (hostf); + hostf = NULL; + } +} + + +/* TCP_LOCATE -- Return a pointer to the first character in the indicated + * character class. + */ +static char * +tcp_locate (cp, match) +register char *cp; +char *match; +{ + register char *mp, c; + + while (c = *cp) { + for (mp = match; *mp; mp++) + if (*mp == c) + return (cp); + cp++; + } + + return ((char *)0); +} diff --git a/unix/os/net/gsocknm.c b/unix/os/net/gsocknm.c new file mode 100644 index 00000000..453fdb70 --- /dev/null +++ b/unix/os/net/gsocknm.c @@ -0,0 +1,23 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" + +extern int errno; +extern int tcperrno; + +/* TCP_GSOCKNAME -- Get socket name. Return the current network name for the + * indicated socket. + */ +tcp_gsockname (s, name, namelen) +u_sock s; /* the socket */ +struct sockaddr *name; /* endpoint of communications */ +int namelen; /* maxlen in; actual len out */ +{ + int status; + + /* MACHDEP */ + status = getsockname (s, name, namelen); + tcperrno = errno; + return (status); +} diff --git a/unix/os/net/hostdb.c b/unix/os/net/hostdb.c new file mode 100644 index 00000000..3006c56e --- /dev/null +++ b/unix/os/net/hostdb.c @@ -0,0 +1,39 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + +/* MACHDEP */ +#define HOSTDB "/etc/hosts" /* change to "" if not UNIX */ + + +/* TCP_HOSTDB -- Return the machine dependent pathname of the host name table + * file. On a Berkeley UNIX host system this is "/etc/hosts", but to avoid + * hidden machine pathnames in the code we reference "iraf$dev/uhosts" instead. + */ +char * +tcp_hostdb() +{ + static char hostdb[SZ_FNAME+1] = HOSTDB; + PKCHAR osfn[SZ_FNAME+1]; + + /* If HOSTDB is explicitly defined, use it, else return OSFN of the + * the file "dev$uhosts". If the filename generation fails (e.g., + * because IRAF is not defined in the host environment) return + * anything. In this case anything is the pathname of the Berkeley + * UNIX hosts file, which will cause a file open failure on most + * systems. + */ + if (hostdb[0] == '\0') { + if (ku_mkfname ("iraf", "dev", "uhosts", osfn, SZ_FNAME) == ERR) + strcpy ((char *)osfn, "/etc/hosts"); + strcpy (hostdb, (char *)osfn); + } + + return (hostdb); +} diff --git a/unix/os/net/htonl.c b/unix/os/net/htonl.c new file mode 100644 index 00000000..e9c57280 --- /dev/null +++ b/unix/os/net/htonl.c @@ -0,0 +1,22 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +/* HTONL -- [MACHDEP] Convert a long integer in host format to net format. + */ +htonl (lword) +long lword; +{ + register char *ip, *op; + static long hostw, netw; + + hostw = lword; + ip = (char *)&hostw; + op = (char *)&netw + 4; + + *--op = *ip++; + *--op = *ip++; + *--op = *ip++; + *--op = *ip++; + + return (netw); +} diff --git a/unix/os/net/htons.c b/unix/os/net/htons.c new file mode 100644 index 00000000..9f390c29 --- /dev/null +++ b/unix/os/net/htons.c @@ -0,0 +1,16 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +/* HTONS -- [MACHDEP] Convert a short integer in host format to net format. + */ +htons (word) +short word; +{ + register char *wp; + static short w; + + w = word; + wp = (char *)&w; + + return ((wp[0] << 8) | wp[1]); +} diff --git a/unix/os/net/in.h b/unix/os/net/in.h new file mode 100644 index 00000000..825a9d39 --- /dev/null +++ b/unix/os/net/in.h @@ -0,0 +1,134 @@ +/* in.h 6.1 83/07/29 */ + +/* + * Constants and structures defined by the internet system, + * Per RFC 790, September 1981. + */ + +/* + * Protocols + */ +#define IPPROTO_ICMP 1 /* control message protocol */ +#define IPPROTO_GGP 2 /* gateway^2 (deprecated) */ +#define IPPROTO_TCP 6 /* tcp */ +#define IPPROTO_PUP 12 /* pup */ +#define IPPROTO_UDP 17 /* user datagram protocol */ +#define IPPROTO_ND 77 /* UNOFFICIAL net disk proto */ + +#define IPPROTO_RAW 255 /* raw IP packet */ +#define IPPROTO_MAX 256 + +/* + * Port/socket numbers: network standard functions + */ +#define IPPORT_ECHO 7 +#define IPPORT_DISCARD 9 +#define IPPORT_SYSTAT 11 +#define IPPORT_DAYTIME 13 +#define IPPORT_NETSTAT 15 +#define IPPORT_FTP 21 +#define IPPORT_TELNET 23 +#define IPPORT_SMTP 25 +#define IPPORT_TIMESERVER 37 +#define IPPORT_NAMESERVER 42 +#define IPPORT_WHOIS 43 +#define IPPORT_MTP 57 + +/* + * Port/socket numbers: host specific functions + */ +#define IPPORT_TFTP 69 +#define IPPORT_RJE 77 +#define IPPORT_FINGER 79 +#define IPPORT_TTYLINK 87 +#define IPPORT_SUPDUP 95 + +/* + * UNIX TCP sockets + */ +#define IPPORT_EXECSERVER 512 +#define IPPORT_LOGINSERVER 513 +#define IPPORT_CMDSERVER 514 +#define IPPORT_EFSSERVER 520 + +/* + * UNIX UDP sockets + */ +#define IPPORT_BIFFUDP 512 +#define IPPORT_WHOSERVER 513 +#define IPPORT_ROUTESERVER 520 /* 520+1 also used */ + +/* + * Ports < IPPORT_RESERVED are reserved for + * privileged processes (e.g. root). + */ +#define IPPORT_RESERVED 1024 + +/* + * Link numbers + */ +#define IMPLINK_IP 155 +#define IMPLINK_LOWEXPER 156 +#define IMPLINK_HIGHEXPER 158 + +/* + * Internet address (old style... should be updated) + */ +struct in_addr { + union { + struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b; + struct { u_short s_w1,s_w2; } S_un_w; + u_long S_addr; + } S_un; +#define s_addr S_un.S_addr /* can be used for most tcp & ip code */ +#define s_host S_un.S_un_b.s_b2 /* host on imp */ +#define s_net S_un.S_un_b.s_b1 /* network */ +#define s_imp S_un.S_un_w.s_w2 /* imp */ +#define s_impno S_un.S_un_b.s_b4 /* imp # */ +#define s_lh S_un.S_un_b.s_b3 /* logical host */ +}; + +/* + * Definitions of bits in internet address integers. + */ +#define IN_CLASSA(i) ((((long)(i))&0x80000000)==0) +#define IN_CLASSA_NET 0xff000000 +#define IN_CLASSA_NSHIFT 24 +#define IN_CLASSA_HOST 0x00ffffff + +#define IN_CLASSB(i) ((((long)(i))&0xc0000000)==0x80000000) +#define IN_CLASSB_NET 0xffff0000 +#define IN_CLASSB_NSHIFT 16 +#define IN_CLASSB_HOST 0x0000ffff + +#define IN_CLASSC(i) ((((long)(i))&0xc0000000)==0xc0000000) +#define IN_CLASSC_NET 0xffffff00 +#define IN_CLASSC_NSHIFT 8 +#define IN_CLASSC_HOST 0x000000ff + +#define INADDR_ANY 0x00000000 + +/* + * Socket address, internet style. + */ +struct sockaddr_in { + short sin_family; + u_short sin_port; + struct in_addr sin_addr; + char sin_zero[8]; +}; + +#if !defined(vax) +/* + * Macros for number representation conversion. + */ +#define ntohl(x) (x) +#define ntohs(x) (x) +#define htonl(x) (x) +#define htons(x) (x) +#endif + +#ifdef KERNEL +extern struct domain inetdomain; +extern struct protosw inetsw[]; +#endif diff --git a/unix/os/net/inetaddr.c b/unix/os/net/inetaddr.c new file mode 100644 index 00000000..9d96d252 --- /dev/null +++ b/unix/os/net/inetaddr.c @@ -0,0 +1,92 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "ctype.h" +#include "types.h" +#include "in.h" + +/* TCP_INETADDR -- Internet address interpretation routine. Decode a network + * address from the host name table. The value returned is in network order. + */ +u_long +tcp_inetaddr (cp) +register char *cp; +{ + register u_long val, base, n; + register char c; + u_long parts[4], *pp = parts; + +again: + /* Collect number up to ``.''. + * Values are specified as for C: + * 0x=hex, 0=octal, other=decimal. + */ + val = 0; base = 10; + if (*cp == '0') + base = 8, cp++; + if (*cp == 'x' || *cp == 'X') + base = 16, cp++; + + while (c = *cp) { + if (isdigit(c)) { + val = (val * base) + (c - '0'); + cp++; + continue; + } + if (base == 16 && isxdigit(c)) { + val = (val << 4) + (c + 10 - (islower(c) ? 'a' : 'A')); + cp++; + continue; + } + break; + } + + if (*cp == '.') { + /* Internet format: + * a.b.c.d + * a.b.c (with c treated as 16-bits) + * a.b (with b treated as 24 bits) + */ + if (pp >= parts + 4) + return (-1); + *pp++ = val, cp++; + goto again; + } + + /* Check for trailing characters. + */ + if (*cp && !isspace(*cp)) + return (-1); + *pp++ = val; + + /* Concoct the address according to + * the number of parts specified. + */ + n = pp - parts; + switch (n) { + + case 1: /* a -- 32 bits */ + val = parts[0]; + break; + + case 2: /* a.b -- 8.24 bits */ + val = (parts[0] << 24) | (parts[1] & 0xffffff); + break; + + case 3: /* a.b.c -- 8.8.16 bits */ + val = (parts[0] << 24) | ((parts[1] & 0xff) << 16) | + (parts[2] & 0xffff); + break; + + case 4: /* a.b.c.d -- 8.8.8.8 bits */ + val = (parts[0] << 24) | ((parts[1] & 0xff) << 16) | + ((parts[2] & 0xff) << 8) | (parts[3] & 0xff); + break; + + default: + return (-1); + } + + val = htonl(val); + return (val); +} diff --git a/unix/os/net/kutil.c b/unix/os/net/kutil.c new file mode 100644 index 00000000..3e7ddb0d --- /dev/null +++ b/unix/os/net/kutil.c @@ -0,0 +1,342 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + + +/* + * KUTIL -- Miscellaneous utilities required by the network interface. + * Most of these are either portable (no i/o) or are built upon the kernel + * i/o routines. + */ + +/* KU_FOPEN -- Open a text file. + */ +ku_fopen (fname, mode) +char *fname; +char *mode; +{ + PKCHAR osfn[SZ_PATHNAME+1]; + XINT fmode, chan; + + strcpy ((char *)osfn, fname); + + if (mode[0] == 'r') + fmode = READ_ONLY; + else + return (ERR); + + ZOPNTX (osfn, &fmode, &chan); + + return (chan); +} + + +/* KU_FCLOSE -- Close a text file. + */ +ku_fclose (fd) +int fd; +{ + XINT chan=fd, status; + + ZCLSTX (&chan, &status); + return (status); +} + + +/* KU_FGETS -- Get a newline delimited line from a text file. The semantics of + * this procedure are like the unix FGETS. + */ +char * +ku_fgets (obuf, maxch, fd) +char *obuf; +int maxch; +int fd; +{ + register XCHAR *ip; + register char *op; + register int n; + XCHAR lbuf[SZ_LINE+1]; + XINT maxchars, status, chan; + + maxchars = (maxch > SZ_LINE) ? SZ_LINE : maxch; + chan = fd; + + ZGETTX (&chan, lbuf, &maxchars, &status); + if (status <= 0) + return (NULL); + + for (ip=lbuf, op=obuf, n=status; --n >= 0; ) + *op++ = *ip++; + *op++ = EOS; + + return (obuf); +} + + +/* KU_GPASSWD -- Read a line from the terminal in raw mode (no echo), e.g., + * when reading a password. + */ +ku_gpasswd (prompt, passwd, maxch) +char *prompt; /* user prompt string */ +char *passwd; /* receives password */ +int maxch; +{ + XCHAR text[SZ_LINE+1], ch; + XINT mode=READ_WRITE, chan, status, nchars; + register char *ip; + register XCHAR *op; + register int n; + + /* Open terminal. */ + strcpy ((char *)text, TTYNAME); + ZOPNTY (text, &mode, &chan); + if (chan < 0) { + passwd[0] = EOS; + return (ERR); + } + + /* Write prompt string. */ + for (ip=prompt, op=text, nchars=0; (*op++ = *ip++) != EOS; ) + nchars++; + ZPUTTY (&chan, text, &nchars, &status); + ZFLSTY (&chan, &status); + + /* Read line in raw mode. */ + nchars = 1; + for (n=0; n < maxch; n++) { + ZGETTY (&chan, &text, &nchars, &status); + ch = text[0]; + if (status <= 0 || ch == '\n' || ch == '\r') + break; + passwd[n] = ch; + } + passwd[n] = EOS; + + /* Echo the newline. */ + ch = '\n'; + ZPUTTY (&chan, &ch, &nchars, &status); + + /* Disable raw mode. */ + nchars = LEN_RAWCMD; + for (ip=RAWOFF, op=text, n=LEN_RAWCMD; --n > 0 && (*op++ = *ip++); ) + ; + ZPUTTY (&chan, text, &nchars, &status); + ZCLSTY (&chan, &status); + + return (n); +} + + +/* KU_MKFNAME -- Make an OSFN, given a logical directory name (either "iraf" + * or "home"), a subdirectory name, and a filename. + */ +ku_mkfname (ldir, subdir, fname, osfn, maxch) +char *ldir; /* logical directory name */ +char *subdir; /* subdirectory */ +char *fname; /* filename */ +char *osfn; /* receives pathname */ +int maxch; +{ + PKCHAR pkname[SZ_PATHNAME+1]; + PKCHAR temp[SZ_FNAME+1]; + XINT maxchars=SZ_PATHNAME, nchars; + + if (ku_mapdir (ldir, (char *)pkname, SZ_PATHNAME) == ERR) + return (ERR); + + strcpy ((char *)temp, subdir); + ku_strupk (pkname, pkname, &maxchars); + ku_strupk (temp, temp, &maxchars); + ZFSUBD (pkname, &maxchars, temp, &nchars); + ku_strpak (pkname, pkname, &maxchars); + + strcat ((char *)pkname, fname); + strncpy (osfn, (char *)pkname, maxch); + osfn[maxch-1] = EOS; + + return (OK); +} + + +/* KU_ITOC -- Encode a simple positive integer in a decimal radix, returning + * a pointer to the encoded numeric string. + */ +char * +ku_itoc (num) +int num; +{ + register int dig, n; + register char *op; + static char buf[15]; + + op = &buf[15]; + *--op = '\0'; + + for (n=num; dig = n % 10; n /= 10) + *--op = dig + '0'; + + return (op); +} + + +/* KU_BCOPY -- Copy a byte array. + */ +ku_bcopy (a, b, nbytes) +char *a; /* input byte array */ +char *b; /* output byte array */ +int nbytes; /* number of bytes to move */ +{ + register char *ip, *op; + register int n = nbytes; + + /* If the two arrays are the same return immediately. If the move is + * to the left then copy left to right, else copy right to left. + */ + if (a == b) { + return; + } else if (b < a) { + for (ip=a, op=b; --n >= 0; ) + *op++ = *ip++; + } else { + for (ip = &a[n], op = &b[n]; --n >= 0; ) + *--op = *--ip; + } +} + + +/* KU_SLEEP -- Suspend process execution. + */ +ku_sleep (nseconds) +int nseconds; +{ + int mseconds = nseconds*1000; + + ZWMSEC (&mseconds); +} + + +/* KU_ERROR -- [MACHDEP] Print an error message somewhere where the user can + * see it (but do not abort or interrupt execution). + */ +ku_error (message) +char *message; +{ + write (2, message, strlen(message)); + write (2, "\n", 1); +} + + +/* KU_MAPDIR -- Return the OSFN of the named logical directory, which can + * be either "iraf" or "home". The IRAF root directory is either given in + * the host system environment and returned by ZGTENV, or is defined as + * IRAF in . The user's login directory "home" is the host + * system login directory, not the IRAF login directory. On a UNIX system + * the pathname of this directory is given in the UNIX file /etc/passwd. + * On other systems, e.g., VMS, the ZGTENV mechanism can be used to define + * the user's home directory. + */ +ku_mapdir (ldir, osfn, maxch) +char *ldir; /* logical directory name */ +char *osfn; /* receives filename */ +int maxch; +{ + PKCHAR pkname[SZ_FNAME+1]; + PKCHAR valstr[SZ_PATHNAME+1]; + XINT maxchars=SZ_PATHNAME, status; + + /* Look in the host environment first. + */ + strcpy ((char *)pkname, ldir); + ZGTENV (pkname, valstr, &maxchars, &status); + + if (status > 0) { + strncpy (osfn, (char *)valstr, maxch); + osfn[maxch-1] = EOS; + return (OK); + } else if (strncmp (ldir, "iraf", 4) == 0) { + strncpy (osfn, IRAF, maxch); + osfn[maxch-1] = EOS; + return (OK); + } else if (strncmp (ldir, "home", 4) != 0) { + osfn[0] = EOS; + return (ERR); + } + + /* If we get here the ldir is "home" and no definition was found in the + * host environment. Determine host login directory by some system + * dependent means. [MACHDEP]. + */ + strcpy ((char *)pkname, "LOGNAME"); + ZGTENV (pkname, valstr, &maxchars, &status); + if (status <= 0) { + osfn[0] = EOS; + return (ERR); + } else { + strcpy (osfn, ":udd:"); + strcat (osfn, (char *)valstr); + strcat (osfn, ":"); + return (OK); + } +} + + +/* STRPAK -- Pack an SPP character string into a C string, i.e., a sequence + * of characters stored one per byte, delimited by EOS='\0'. The operation + * may be performed in place. This version assumes that the host character + * set is ASCII and hence no lookup table reference to map character sets is + * needed. If this is not the case, code must be added to convert to the host + * character set. + * + * N.B.: If sizeof(XCHAR)=1, XEOS=EOS, and the host character set is ASCII, + * and the operation is being performed in place, then this procedure should + * do nothing. + */ +ku_strpak (instr, outstr, maxch) +XCHAR *instr; +PKCHAR *outstr; +XINT *maxch; +{ + register XCHAR *ip = instr; + register char *op = (char *)outstr; + register int n = *maxch; + + while ((*op++ = *ip++) != XEOS && --n >= 0) + ; + *--op = EOS; +} + +/* STRUPK -- Unpack a kernel (C style) string into an SPP string. The unpacking * operation can be performed in place. A kernel string consists of a sequence + * of host characters stored one character per byte, delimited by EOS='\0'. + * We assume here that the host character set is ASCII. If this is not the + * case code must be added to convert from the host character set to ASCII in + * the unpacked string. + * + * N.B.: If sizeof(XCHAR)=1, XEOS=EOS, and the host character set is ASCII, + * and the operation is being performed in place, then this procedure should + * do nothing. + */ +ku_strupk (instr, outstr, maxch) +PKCHAR *instr; +XCHAR *outstr; +XINT *maxch; +{ + register char *ip = (char *)instr; + register XCHAR *op = outstr; + register int n; + + /* Is is necessary to determine the length of the string in order to + * be able to unpack the string in place, i.e., from right to left. + */ + n = strlen (ip); + n = (n < *maxch) ? n : *maxch; + op[n] = XEOS; + + while (--n >= 0) + op[n] = ip[n]; +} diff --git a/unix/os/net/listen.c b/unix/os/net/listen.c new file mode 100644 index 00000000..02f75651 --- /dev/null +++ b/unix/os/net/listen.c @@ -0,0 +1,22 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" + +extern int errno; +extern int tcperrno; + +/* TCP_LISTEN -- Listen for connections on a socket. Returns immediately, + * i.e., listen does not block the calling process. + */ +tcp_listen (s, backlog) +u_sock s; /* the socket */ +int backlog; /* max queued connects */ +{ + int status; + + /* MACHDEP */ + status = listen (s, backlog); + tcperrno = errno; + return (status); +} diff --git a/unix/os/net/mkpkg b/unix/os/net/mkpkg new file mode 100644 index 00000000..a534884b --- /dev/null +++ b/unix/os/net/mkpkg @@ -0,0 +1,25 @@ +# Make the ZFIOKS-REXEC-TCP/IP network interface package. + +$set XFLAGS = "-c $(HSI_XF)" + +libos.a: + accept.c types.h + connect.c types.h + ghostbynm.c netdb.h + ghostent.c types.h netdb.h socket.h + gsocknm.c types.h + hostdb.c + htonl.c + htons.c + inetaddr.c ctype.h in.h types.h + kutil.c + listen.c types.h + ntohl.c + ntohs.c + rexec.c in.h netdb.h socket.h types.h + socket.c types.h + tcpclose.c types.h + tcpread.c types.h + tcpwrite.c types.h + zfioks.c ctype.h types.h in.h + ; diff --git a/unix/os/net/netdb.h b/unix/os/net/netdb.h new file mode 100644 index 00000000..cddd2305 --- /dev/null +++ b/unix/os/net/netdb.h @@ -0,0 +1,44 @@ +/* %M% %I% %E% */ +/* + * Structures returned by network + * data base library. All addresses + * are supplied in host order, and + * returned in network order (suitable + * for use in system calls). + */ +struct hostent { + char *h_name; /* official name of host */ + char **h_aliases; /* alias list */ + int h_addrtype; /* host address type */ + int h_length; /* length of address */ + char *h_addr; /* address */ +}; + +/* + * Assumption here is that a network number + * fits in 32 bits -- probably a poor one. + */ +struct netent { + char *n_name; /* official name of net */ + char **n_aliases; /* alias list */ + int n_addrtype; /* net address type */ + int n_net; /* network # */ +}; + +struct servent { + char *s_name; /* official service name */ + char **s_aliases; /* alias list */ + int s_port; /* port # */ + char *s_proto; /* protocol to use */ +}; + +struct protoent { + char *p_name; /* official protocol name */ + char **p_aliases; /* alias list */ + int p_proto; /* protocol # */ +}; + +struct hostent *gethostbyname(), *gethostbyaddr(), *gethostent(); +struct netent *getnetbyname(), *getnetbyaddr(), *getnetent(); +struct servent *getservbyname(), *getservbyport(), *getservent(); +struct protoent *getprotobyname(), *getprotobynumber(), *getprotoent(); diff --git a/unix/os/net/ntohl.c b/unix/os/net/ntohl.c new file mode 100644 index 00000000..34d6b07a --- /dev/null +++ b/unix/os/net/ntohl.c @@ -0,0 +1,22 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +/* NTOHL -- [MACHDEP] Convert a long integer in net format to host format. + */ +ntohl (lword) +long lword; +{ + register char *ip, *op; + static long hostw, netw; + + netw = lword; + ip = (char *)&netw; + op = (char *)&hostw + 4; + + *--op = *ip++; + *--op = *ip++; + *--op = *ip++; + *--op = *ip++; + + return (hostw); +} diff --git a/unix/os/net/ntohs.c b/unix/os/net/ntohs.c new file mode 100644 index 00000000..02a956a6 --- /dev/null +++ b/unix/os/net/ntohs.c @@ -0,0 +1,16 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +/* NTOHS -- [MACHDEP] Convert a short integer in net format to host format. + */ +ntohs (word) +short word; +{ + register char *wp; + static short w; + + w = word; + wp = (char *)&w; + + return ((wp[0] << 8) | wp[1]); +} diff --git a/unix/os/net/rexec.c b/unix/os/net/rexec.c new file mode 100644 index 00000000..4f851fb9 --- /dev/null +++ b/unix/os/net/rexec.c @@ -0,0 +1,160 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" +#include "socket.h" +#include "in.h" +#include "netdb.h" + +/* TCP_REXEC -- Execute a command on a remote node via the network. This is + * an implementation of the Berkeley UNIX procedure of the same name for a + * machine independent TCP network interface. Unlike the UNIX rexec, + * however, we require that the user login name and password be given as + * input arguments, in addition to the host name and port number and the + * command to be executed. + * + * REXEC assumes that it is talking to an REXECD server on the remote node. + * The TCP EXEC port on the remote node spawns the REXECD server which reads and + * authenticates the user login and password, sets up the error socket if so + * indicated, changes the current directory to the user's home directory, and + * then executes the command. The command syntax is determined by the shell + * REXECD spawns to execute the command, and is implementation dependent. + * Currently the REXECD daemons are either UNIX hosted or UNIX emulated, hence + * the command syntax is the UNIX shell (Bourne shell usually). The command + * executes with its standard input and output (and error output if fd2p=0) + * connected to the socket returned by REXEC. + * + * Note that the shell spawned by the REXEC daemon may be used to spawn a user + * specified server process using a shell command, e.g. "run server.e arg arg". + * In this case the daemon is used to login and set the current directory and + * pass args to the user server, at the expense of one additional process spawn + * for the shell. + */ +tcp_rexec (ahost, rport, name, pass, cmd, fd2p) +char **ahost; /* alias of server node */ +int rport; /* IP port number (for EXEC) */ +char *name, *pass; /* user login and password */ +char *cmd; /* command to be executed */ +int *fd2p; /* error channel */ +{ + struct hostent *tcp_gethostbyname(); + struct sockaddr_in sin, sin2, from; + struct hostent *hp; + int timo = 1; + u_sock s, s3; + char c; + short port; +eprintf("rexec %s %s %s %s\n", *ahost, name, pass, cmd); + + /* Read host name table for the local network to get the internet + * address of the named host. + */ + hp = tcp_gethostbyname (*ahost); + if (hp == 0) { + ku_error ("unknown network host"); + return (-1); + } + + /* Set up a full duplex TCP socket to the TCP/EXEC server process + * on the remote node. + */ +retry: + s = tcp_socket (AF_INET, SOCK_STREAM, 0); + if (s < 0) { + ku_error ("rexec: cannot make socket"); + return (-1); + } + + sin.sin_family = hp->h_addrtype; + sin.sin_port = rport; + ku_bcopy (hp->h_addr, (caddr_t)&sin.sin_addr, hp->h_length); + + if (tcp_connect (s, &sin, sizeof(sin)) < 0) { + if (timo <= 16) { + tcp_close (s); + ku_sleep (timo); + timo *= 2; + goto retry; + } + ku_error ("rexec: connect failure"); + return (-1); + } + + /* If no output error channel variable was given instruct the REXECD + * server to return error output on the data socket, else open a second + * socket to be used for error communications and signals. + */ + if (fd2p == 0) { + tcp_write (s, "", 1); + port = 0; + + } else { + char *num, *ku_itoc(); + int sin2len, len; + u_sock s2; + + s2 = tcp_socket (AF_INET, SOCK_STREAM, 0); + if (s2 < 0) { + tcp_close (s); + return (-1); + } + + tcp_listen (s2, 1); + + sin2len = sizeof (sin2); + if (tcp_gsockname (s2, (char *)&sin2, &sin2len) < 0 || + sin2len != sizeof (sin2)) { + + ku_error ("rexec: getsockname failed"); + tcp_close (s2); + goto bad; + } + + port = htons ((u_short)sin2.sin_port); + num = ku_itoc (port); + tcp_write (s, num, strlen(num)+1); + len = sizeof (from); + + s3 = tcp_accept (s2, &from, &len, 0); + + tcp_close (s2); + if (s3 < 0) { + ku_error ("rexec: accept failure"); + port = 0; + goto bad; + } + + *fd2p = s3; + } + + tcp_write (s, name, strlen (name) + 1); + tcp_write (s, pass, strlen (pass) + 1); + tcp_write (s, cmd, strlen (cmd) + 1); + + if (tcp_read (s, &c, 1) != 1) { + ku_error ("rexec: cannot read server"); + goto bad; + } + + /* Read error message from server process. + */ + if (c != 0) { + char lbuf[80]; + char *op; + + for (op=lbuf; (tcp_read (s, op, 1) == 1); op++) + if (*op == '\n') + break; + *op = '\0'; + ku_error (lbuf); + goto bad; + } + + return (s); +bad: + if (port) + tcp_close (*fd2p); + tcp_close (s); + + return (-1); +} diff --git a/unix/os/net/socket.c b/unix/os/net/socket.c new file mode 100644 index 00000000..c5872fee --- /dev/null +++ b/unix/os/net/socket.c @@ -0,0 +1,25 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" + +extern int errno; +extern int tcperrno; + +/* TCP_SOCKET -- Create an endpoint for communications (a socket) and bind the + * socket to an i/o descriptor, returning the descriptor as the function value. + */ +u_sock +tcp_socket (af, type, protocol) +int af; /* address format, e.g, AF_INET */ +int type; /* socket type, e.g., SOCK_STREAM */ +int protocol; /* communications protocol, if used */ +{ + u_sock s; + + /* MACHDEP */ +eprintf ("socket\n"); + s = socket (af, type, protocol); + tcperrno = errno; + return (s); +} diff --git a/unix/os/net/socket.h b/unix/os/net/socket.h new file mode 100644 index 00000000..ed399681 --- /dev/null +++ b/unix/os/net/socket.h @@ -0,0 +1,109 @@ +/* socket.h 6.1 83/07/29 */ + +/* + * Definitions related to sockets: types, address families, options. + */ + +/* + * Types + */ +#define SOCK_STREAM 1 /* stream socket */ +#define SOCK_DGRAM 2 /* datagram socket */ +#define SOCK_RAW 3 /* raw-protocol interface */ +#define SOCK_RDM 4 /* reliably-delivered message */ +#define SOCK_SEQPACKET 5 /* sequenced packet stream */ + +/* + * Option flags per-socket. + */ +#define SO_DEBUG 0x01 /* turn on debugging info recording */ +#define SO_ACCEPTCONN 0x02 /* socket has had listen() */ +#define SO_REUSEADDR 0x04 /* allow local address reuse */ +#define SO_KEEPALIVE 0x08 /* keep connections alive */ +#define SO_DONTROUTE 0x10 /* just use interface addresses */ + /* 0x20 was SO_NEWFDONCONN */ +#define SO_USELOOPBACK 0x40 /* bypass hardware when possible */ +#define SO_LINGER 0x80 /* linger on close if data present */ +#define SO_DONTLINGER (~SO_LINGER) /* ~SO_LINGER */ + +/* + * Address families. + */ +#define AF_UNSPEC 0 /* unspecified */ +#define AF_UNIX 1 /* local to host (pipes, portals) */ +#define AF_INET 2 /* internetwork: UDP, TCP, etc. */ +#define AF_IMPLINK 3 /* arpanet imp addresses */ +#define AF_PUP 4 /* pup protocols: e.g. BSP */ +#define AF_CHAOS 5 /* mit CHAOS protocols */ +#define AF_NS 6 /* XEROX NS protocols */ +#define AF_NBS 7 /* nbs protocols */ +#define AF_ECMA 8 /* european computer manufacturers */ +#define AF_DATAKIT 9 /* datakit protocols */ +#define AF_CCITT 10 /* CCITT protocols, X.25 etc */ +#define AF_SNA 11 /* IBM SNA */ + +#define AF_MAX 12 + +/* + * Structure used by kernel to store most + * addresses. + */ +struct sockaddr { + u_short sa_family; /* address family */ + char sa_data[14]; /* up to 14 bytes of direct address */ +}; + +/* + * Structure used by kernel to pass protocol + * information in raw sockets. + */ +struct sockproto { + u_short sp_family; /* address family */ + u_short sp_protocol; /* protocol */ +}; + +/* + * Protocol families, same as address families for now. + */ +#define PF_UNSPEC AF_UNSPEC +#define PF_UNIX AF_UNIX +#define PF_INET AF_INET +#define PF_IMPLINK AF_IMPLINK +#define PF_PUP AF_PUP +#define PF_CHAOS AF_CHAOS +#define PF_NS AF_NS +#define PF_NBS AF_NBS +#define PF_ECMA AF_ECMA +#define PF_DATAKIT AF_DATAKIT +#define PF_CCITT AF_CCITT +#define PF_SNA AF_SNA + +#define PF_MAX 12 + +/* + * Level number for (get/set)sockopt() to apply to socket itself. + */ +#define SOL_SOCKET 0xffff /* options for socket level */ + +/* + * Maximum queue length specifiable by listen. + */ +#define SOMAXCONN 5 + +/* + * Message header for recvmsg and sendmsg calls. + */ +struct msghdr { + caddr_t msg_name; /* optional address */ + int msg_namelen; /* size of address */ + struct iovec *msg_iov; /* scatter/gather array */ + int msg_iovlen; /* # elements in msg_iov */ + caddr_t msg_accrights; /* access rights sent/received */ + int msg_accrightslen; +}; + +#define MSG_OOB 0x1 /* process out-of-band data */ +#define MSG_PEEK 0x2 /* peek at incoming message */ +#define MSG_DONTROUTE 0x4 /* send without using routing tables */ + +#define MSG_MAXIOVLEN 16 diff --git a/unix/os/net/tcpclose.c b/unix/os/net/tcpclose.c new file mode 100644 index 00000000..0da1cc73 --- /dev/null +++ b/unix/os/net/tcpclose.c @@ -0,0 +1,16 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" + +extern int errno; +extern int tcperrno; + +/* TCP_CLOSE -- Close a socket. + */ +tcp_close (s) +u_sock s; /* the socket */ +{ + /* MACHDEP */ + return (close (s)); +} diff --git a/unix/os/net/tcpread.c b/unix/os/net/tcpread.c new file mode 100644 index 00000000..3a7162d3 --- /dev/null +++ b/unix/os/net/tcpread.c @@ -0,0 +1,26 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" + +extern int errno; +extern int tcperrno; + +/* TCP_READ -- Read from a socket. + */ +tcp_read (s, buf, maxbytes) +u_sock s; /* input socket */ +char *buf; /* output buffer */ +int maxbytes; /* max bytes to read */ +{ + int nbytes; + + /* MACHDEP */ +eprintf ("read %d bytes\n", maxbytes); + + nbytes = read (s, buf, maxbytes); +eprintf ("\t%d bytes read\n", nbytes); + + tcperrno = errno; + return (nbytes); +} diff --git a/unix/os/net/tcpwrite.c b/unix/os/net/tcpwrite.c new file mode 100644 index 00000000..c0a946d3 --- /dev/null +++ b/unix/os/net/tcpwrite.c @@ -0,0 +1,23 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include "types.h" + +extern int errno; +extern int tcperrno; + +/* TCP_WRITE -- Write to a socket. + */ +tcp_write (s, buf, nbytes) +u_sock s; /* output socket */ +char *buf; /* input buffer */ +int nbytes; /* num bytes to write */ +{ + /* MACHDEP */ +eprintf ("write %d bytes\n", nbytes); + nbytes = write (s, buf, nbytes); +eprintf ("%d bytes written\n", nbytes); + + tcperrno = errno; + return (nbytes); +} diff --git a/unix/os/net/types.h b/unix/os/net/types.h new file mode 100644 index 00000000..3110398c --- /dev/null +++ b/unix/os/net/types.h @@ -0,0 +1,39 @@ +/* types.h 6.1 83/07/29 */ + +/* + * Basic system types and major/minor device constructing/busting macros. + */ + +/* major part of a device */ +#define major(x) ((int)(((unsigned)(x)>>8)&0377)) + +/* minor part of a device */ +#define minor(x) ((int)((x)&0377)) + +/* make a device number */ +#define makedev(x,y) ((dev_t)(((x)<<8) | (y))) + +typedef unsigned char u_char; +typedef unsigned short u_short; +typedef unsigned int u_int; +typedef unsigned long u_long; +typedef unsigned short ushort; /* sys III compat */ +typedef unsigned int u_sock; /* TCP/IP */ + +#ifdef vax +typedef struct _physadr { int r[1]; } *physadr; +typedef struct label_t { + int val[14]; +} label_t; +#endif +typedef struct _quad { long val[2]; } quad; +typedef long daddr_t; +typedef char * caddr_t; +typedef u_long ino_t; +typedef long swblk_t; +typedef int size_t; +typedef int time_t; +typedef short dev_t; +typedef int off_t; + +typedef struct fd_set { int fds_bits[1]; } fd_set; diff --git a/unix/os/net/zfioks.c b/unix/os/net/zfioks.c new file mode 100644 index 00000000..1db9cc1e --- /dev/null +++ b/unix/os/net/zfioks.c @@ -0,0 +1,441 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include + +#include "types.h" +#include "in.h" + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_spp +#include + +/* ZFIOKS -- File i/o to a remote kernel server. This driver is the network + * interface for the kernel interface package (sys$ki). The KS driver is + * normally called directly by the KI routines, but is patterned after the + * regular FIO drivers hence may be connected to FIO to provide a network + * interface to the high level code. + * + * zopcks open kernel server on remote node + * zclcks close kernel server + * zardks read from the remote kernel server + * zawrks write to the remote kernel server + * zawtks wait for i/o + * zsttks get channel/device status + * + * The network interface used is an emulation of the Berkeley UNIX function + * REXEC on top of a standard TCP/IP interface. + */ + +#define SZ_NAME 32 /* max size node, etc. name */ +#define SZ_CMD 128 /* max size rexec sh command */ +#define FNNODE_CHAR '!' /* node name delimiter */ +#define HOSTLOGIN "hostlogin" /* user host login file */ +#define IRAFHOSTS ".irafhosts" /* default host login file (dev$) */ +#define USER "" /* symbol for user's login name */ + +int ks_ionbytes[MAXOFILES]; /* nbytes read|written on channel */ +static jmp_buf jmpbuf; +static int recursion = 0; + + +/* ZOPNKS -- Open a connected subprocess on a remote node. Parse the "server" + * argument to obtain the node name and the command to be issued to connect the + * remote process. Call REXEC to exec the remote process and set up a socket + * to be used for CLIN, CLOUT to the remote process. The "server" string is + * implementation dependent and normally comes from the file "dev$hosts" on each + * node. This file is read by the high level code before we are called. + */ +ZOPNKS (server, mode, chan) +PKCHAR *server; /* node name ! command */ +XINT *mode; /* access mode (not used) */ +XINT *chan; /* receives channel code (socket) */ +{ + register char *ip; + char username[SZ_NAME+1], password[SZ_NAME+1]; + char *host, *cmd; + int ipport; + + /* Extract the host name and remote process spawn command from the + * server string, format "host!cmd", e.g., "2!/iraf/lib/irafks.e". + * If the server is "host", we are being called from a server process + * to set up communications with the host process. The UNIX rexec + * connects the host to the process standard input and output, hence + * if the server is "host" the channels are already active. + */ + if (strcmp ((char *)server, "host") == 0) { + *chan = 0; + return; + } + + host = (char *)server; + cmd = NULL; + + for (ip = (char *)server; *ip != EOS; ip++) + if (*ip == FNNODE_CHAR) { + *ip = EOS; + cmd = ip + 1; + break; + } + if (cmd == NULL) { + *chan = ERR; + return; + } + + /* Get login name and password and connect to the kernel server + * process. TCP_REXEC is a portable version of the Berkeley UNIX + * REXEC facility (see ./net). + */ + if (ks_getlogin (host, username, password) == ERR) + *chan = ERR; + else { + ipport = htons (IPPORT_EXECSERVER); + *chan = tcp_rexec (&host, ipport, username, password, cmd, 0); + } + + if (*chan > 0) + ks_ionbytes[*chan] = 0; +} + + +/* KS_GETLOGIN -- Get the user's login name and password, required for + * authentication on the remote machine. We could get these from the unix + * password file on the local machine, but there is no guarantee that the + * login name and password would be the same on a remote node as on the + * local machine. Instead we look in the user's unix login directory for + * the file ".irafhosts". If this file cannot be opened or if it does not + * contain an entry for the named node we use a default public login. The + * public login provides sufficient priviledge for most operations but will + * not provide write access to the user's files on the remote node. + */ +ks_getlogin (node, username, password) +char *node; /* node we wish a login for */ +char *username; /* receives the login name */ +char *password; /* receives the login password */ +{ + char fname[SZ_FNAME+1]; + char uname[SZ_FNAME+1]; + + /* Get the user login name on the local node, used as the default + * login for remote nodes. [MACHDEP - edit for local system] + */ + strcpy (uname, "USER"); + + /* Try to open the .irafhosts file in the user's login directory. + */ + if (ku_mkfname ("home", "", IRAFHOSTS, fname, SZ_FNAME) != ERR) + if (ks_scanlogin (fname, node, uname, username, password) == OK) + return (OK); + + /* Scan the dev$hostlogin file and return a default public login + * on the remote node. + */ + if (ku_mkfname ("iraf", "dev", HOSTLOGIN, fname, SZ_FNAME) != ERR) + return (ks_scanlogin (fname, node, uname, username, password)); + + return (ERR); +} + + +/* KS_SCANLOGIN -- Open and scan a host login file, returning the login + * name and password to be used on the named node. The format of the table + * is a series of lines of the form + * + * alias1 alias2 ... aliasN : loginname password + * + * If the same login name and password are used on several nodes, a single + * entry may be given for all. If the alias "*" is encountered scanning stops + * and the next login name and password are used. The table file should of + * course be protected from reading except by the owner. If even this is + * considered too dangerous, the password "?" may be given in the table and a + * runtime query will result - this will fail if one is no longer logged in. + */ +ks_scanlogin (fname, node, uname, username, password) +char *fname; /* table file */ +char *node; /* node name */ +char *uname; /* user login on local node */ +char *username; /* receives user login name */ +char *password; /* receives user password */ +{ + char *ip; + char lbuf[SZ_LINE+1]; + char wbuf[SZ_NAME+1]; + int fp; + int foundit; + char *ku_fgets(); + + foundit = 0; + if ((fp = ku_fopen (fname, "r")) == ERR) + return (ERR); + + /* Scan file for line containing node name. + */ + while (!foundit && ku_fgets (lbuf, SZ_LINE, fp) != NULL) { + /* Skip blank lines and comment lines */ + for (ip=lbuf; *ip == ' ' || *ip == '\t'; ip++) + ; + if (*ip == '#' || *ip == EOS) + continue; + + /* Scan list of aliases */ + while (ks_getword (&ip, wbuf) > 0) { + if (strcmp (wbuf, ":") == 0) { + break; + } else if (strcmp(wbuf,"*")==0 || strcmp(wbuf,node)==0) { + foundit++; + break; + } + } + } + + ku_fclose (fp); + if (!foundit) + return (ERR); + + /* Skip to end of alias list. */ + while (ks_getword (&ip, wbuf) > 0) { + if (strcmp (wbuf, ":") == 0) { + /* Return login name and password. + */ + + /* If the login name is given as the USER string, use the + * login name on the local node. If the login name is given + * as "?", query the user for the actual login name. + */ + if (ks_getword (&ip, username) <= 0) + return (ERR); + if (strcmp (username, USER) == 0) + strcpy (username, uname); + else if (strcmp (username, "?") == 0) { + char prompt[80]; + + sprintf (prompt, "Login name (%s@%s): ", username, node); + if (ku_gpasswd (prompt, username, SZ_NAME) == ERR) + return (ERR); + } + + /* If the password is given as "?", query the user for + * the actual password. + */ + if (ks_getword (&ip, password) <= 0) + return (ERR); + if (strcmp (password, "?") == 0) { + char prompt[80]; + + sprintf (prompt, "Password (%s@%s): ", username, node); + if (ku_gpasswd (prompt, password, SZ_NAME) == ERR) + return (ERR); + } + + return (OK); /* SUCCESS */ + } + } + + return (ERR); +} + + +/* KS_GETWORD -- Get the next whitespace or : delimited word from the + * input string. + */ +ks_getword (ip, obuf) +char **ip; /* pointer into input buffer */ +char *obuf; /* receives name */ +{ + register char *cp, *op; + register int n; + + for (cp = *ip; isspace(*cp); cp++) + ; + + op = obuf; + n = 0; + + if (*cp == ':' || *cp == '*' || *cp == '?') { + *op++ = *cp++; + n++; + } else { + while (*cp && !isspace(*cp) && !(*cp==':' || *cp=='*' || *cp=='?')) + if (n++ >= SZ_NAME) + return (ERR); + else + *op++ = *cp++; + } + + *op = EOS; + *ip = cp; + + return (n); +} + + +/* ZCLSKS -- Close a kernel server connection. + */ +ZCLSKS (chan, status) +XINT *chan; /* socket to kernel server */ +XINT *status; /* receives close status */ +{ + *status = tcp_close (*chan); +} + + +/* ZARDKS -- Read from the kernel server channel. No attempt is made to + * impose a record structure upon the channel, as is the case with IPC. + * In UNIX the channel is stream oriented and it is up to the caller to + * unblock records from the input stream. Data blocks are assumed to be + * preceded by headers telling how much data to read, hence we read from + * the channel until the specified number of bytes have been read or ERR + * or EOF is seen on the stream. + */ +ZARDKS (chan, buf, totbytes, loffset) +XINT *chan; /* kernel server channel (socket) */ +XCHAR *buf; /* output buffer */ +XINT *totbytes; /* total number of bytes to read */ +XLONG *loffset; /* not used */ +{ + register char *op; + register int fd, nbytes; + int (*sigint)(), (*sigterm)(); + int status; + extern pr_onsig(); + + fd = *chan; + op = (char *)buf; + ks_ionbytes[fd] = nbytes = *totbytes; + + /* Now read exactly nbytes of data from channel into user buffer. + * Return actual byte count if EOF is seen. If ERR is seen return + * ERR. If necessary multiple read requests are issued to read the + * entire record. Reads are interruptable but the interrupt is caught + * and returned as a read error on the server channel. + */ + sigint = signal (SIGINT, pr_onsig); + sigterm = signal (SIGTERM, pr_onsig); + + while (nbytes > 0) { + if (setjmp (jmpbuf) == 0) + status = tcp_read (fd, op, nbytes); + else + status = ERR; + + switch (status) { + case 0: + ks_ionbytes[fd] -= nbytes; + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); + return; + case ERR: + ks_ionbytes[fd] = ERR; + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); + return; + default: + nbytes -= status; + op += status; + break; + } + } + + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); +} + + +/* ZAWRKS -- Write to a kernel server channel. + */ +ZAWRKS (chan, buf, totbytes, loffset) +XINT *chan; /* kernel server channel (socket) */ +XCHAR *buf; /* output buffer */ +XINT *totbytes; /* number of bytes to write */ +XLONG *loffset; /* not used */ +{ + register int fd, ofd, nbytes; + int (*sigint)(), (*sigterm)(), (*sigpipe)(); + extern pr_onsig(); + + /* If chan=0 (the process standard input) then we really want to + * write to channel 1, the standard output. + */ + if ((ofd = fd = *chan) == 0) + ofd = 1; + + ks_ionbytes[fd] = nbytes = *totbytes; + + /* Write exactly nbytes of data to the channel from user buffer to + * the channel. Block interrupt during the write to avoid corrupting + * the data stream protocol if the user interrupts the client task. + * Trap SIGPIPE and return it as a write error on the channel instead. + * Likewise, turn an interrupt into a write error on the channel. + */ + sigint = signal (SIGINT, pr_onsig); + sigterm = signal (SIGTERM, pr_onsig); + sigpipe = signal (SIGPIPE, pr_onsig); + recursion = 0; + + if (setjmp (jmpbuf) == 0) + ks_ionbytes[fd] = tcp_write (ofd, (char *)buf, nbytes); + else + ks_ionbytes[fd] = ERR; + + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); + signal (SIGPIPE, sigpipe); +} + + +/* PR_ONSIG -- Catch a signal and make it look like a write error on the + * server i/o channel. + */ +pr_onsig (sig, code, scp) +int sig; /* signal which was trapped */ +int code; /* subsignal code (vax) */ +struct sigcontext *scp; /* not used */ +{ + if (sig == SIGPIPE && recursion++ == 0) + ku_error ("kernel server process has died"); + + longjmp (jmpbuf, sig); +} + + +/* ZAWTKS -- Wait for i/o to a KS channel. Since UNIX i/o is not asynchronous + * we do not really wait, rather we return the status value (byte count) from + * the last read or write to the channel. + */ +ZAWTKS (chan, status) +XINT *chan; +XINT *status; +{ + if ((*status = ks_ionbytes[*chan]) == ERR) + *status = XERR; +} + + +/* ZSTTKS -- Get binary file status for an KS channel. A KS channel is a + * streaming binary file. + */ +ZSTTKS (chan, param, lvalue) +XINT *chan; /* not used; all KS channels have same status */ +XINT *param; +XLONG *lvalue; +{ + switch (*param) { + case FSTT_BLKSIZE: + case FSTT_FILSIZE: + *lvalue = 0; + break; + case FSTT_OPTBUFSIZE: + *lvalue = KS_OPTBUFSIZE; + break; + case FSTT_MAXBUFSIZE: + *lvalue = KS_MAXBUFSIZE; + break; + default: + *lvalue = XERR; + } +} diff --git a/unix/os/net/zzdebug.x b/unix/os/net/zzdebug.x new file mode 100644 index 00000000..6d22d29d --- /dev/null +++ b/unix/os/net/zzdebug.x @@ -0,0 +1,92 @@ +# Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + +task rexec = t_rexec, + rtype = t_rtype, + rread = t_rread + +define SZ_BUF 4096 + + +# REXEC -- Execute a command on a remote node and print the resultant output on +# the standard output. Used to test the kernel server driver. + +procedure t_rexec() + +char server[SZ_LINE] +char buf[SZ_BUF] +int chan, nbytes, status + +begin + call clgstr ("server", server, SZ_LINE) + call strpak (server, server, SZ_LINE) + + call zopnks (server, READ_WRITE, chan) + if (chan == ERR) + call error (1, "cannot connect to remote server process") + + repeat { + call zardks (chan, buf, SZ_BUF, 0) + call zawtks (chan, nbytes) + + if (nbytes > 0) { + call chrupk (buf, 1, buf, 1, nbytes) + call write (STDOUT, buf, nbytes) + call flush (STDOUT) + } + } until (nbytes <= 0) + + call zclsks (chan, status) + if (status == ERR) + call error (1, "error disconnecting server process") +end + + +# RTYPE -- Type a text file possibly resident on a remote node. + +procedure t_rtype() + +char fname[SZ_FNAME] +char lbuf[SZ_LINE] +int fd +int open(), getline() + +begin + call clgstr ("file", fname, SZ_FNAME) + fd = open (fname, READ_ONLY, TEXT_FILE) + + while (getline (fd, lbuf) != EOF) { + call putline (STDOUT, lbuf) + call flush (STDOUT) + } + + call close (fd) +end + + +# RREAD -- Read a binary file. + +procedure t_rread() + +char fname[SZ_FNAME] +char dbuf[SZ_BUF] +int fd +long nchars, totchars +int open(), read() + +begin + call clgstr ("file", fname, SZ_FNAME) + fd = open (fname, READ_ONLY, BINARY_FILE) + + totchars = 0 + + repeat { + nchars = read (fd, dbuf, SZ_BUF) + if (nchars > 0) + totchars = totchars + nchars + } until (nchars == EOF) + + call close (fd) + + call printf ("read %d chars\n") + call pargi (totchars) +end diff --git a/unix/os/prwait.c b/unix/os/prwait.c new file mode 100644 index 00000000..381d87b1 --- /dev/null +++ b/unix/os/prwait.c @@ -0,0 +1,175 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + +/* Process table code. The high level code assumes that it can open and close + * processes in any order. The UNIX "wait" primitive, called when a process is + * closed, returns the process status and pid of the first process to exit. + * Hence several calls to wait may be necessary to wait for a given process to + * exit. We hide all this behind the pr_wait call, which waits for a PARTICULAR + * process to exit and returns its exit status. + * + * NOT INTERFACE PROCEDURES. This code is only called internally by other + * kernel procedures. All primitives which execute subprocesses, i.e., ZOPCPR, + * ZOPDPR, ZOSCMD, etc. must call these routines. + */ + +struct proctable { + int pr_pid; /* process id */ + int pr_active; /* if YES, process is still active */ + int pr_inchan; /* input IPC channel */ + int pr_outchan; /* output IPC channel */ + int pr_exit_status; /* process exit_status */ +} prtable[MAXPROCS]; + +extern int errno; + +#ifdef MACOSX +#define POSIX +#endif + + +/* PR_ENTER -- Make a new entry in the process table. Something is very wrong + * if the table overflows. + */ +void +pr_enter (int pid, int inchan, int outchan) +{ + register struct proctable *pr; + struct proctable *pr_findpid(); + + extern int kernel_panic (char *msg); + + + if ((pr = pr_findpid (NULL)) == NULL) + kernel_panic ("iraf process table overflow"); + else { + pr->pr_pid = pid; + pr->pr_active = YES; + pr->pr_inchan = inchan; + pr->pr_outchan = outchan; + } +} + + +/* PR_WAIT -- Wait for the process associated with the given pid to terminate + * and return it's exit status. If there is no such process in the table + * return ERR. The table entry is cleared by this call. + */ +int +pr_wait (int pid) +{ + register struct proctable *pr; + int error_code; + pid_t waitpid; + struct proctable *pr_findpid(); +#ifdef POSIX + int exit_status; +#else + union wait exit_status; +#endif + + + /* Lookup process in table. Return ERR if there is no entry. + */ + if ((pr = pr_findpid (pid)) == NULL) + return (ERR); + + if (pr->pr_active == NO) { + /* Process has already terminated. Clear table entry and return + * exit status (set in a previous call). + */ + pr->pr_pid = (int) 0; + return (pr->pr_exit_status); + + } else { + /* Process is in table but has not yet terminated. Call wait until + * the process exits. If other processes exit in the meantime + * save their exit status in the table and mark them inactive. + * If an unknown process terminates ignore it; this will happen + * when a killed bkg process terminates after its process slot + * has been released. + */ + while ((waitpid = wait (&exit_status)) != ERR) { + if ((pr = pr_findpid (waitpid)) != NULL) { + pr->pr_active = NO; + + /* The integer argument to exit() is returned in the + * wait struct defined in . + */ +#ifdef POSIX + error_code = WEXITSTATUS(exit_status); +#else + error_code = exit_status.w_T.w_Retcode; +#endif + pr->pr_exit_status = error_code ? error_code : XOK; + + if (waitpid == pid) { + pr->pr_pid = (int) 0; + return (pr->pr_exit_status); + } + } + } + return (ERR); + } +} + + +/* PR_GETIPC -- Get the codes for the IPC channels assigned to a process. + */ +int +pr_getipc (int pid, int *inchan, int *outchan) +{ + register struct proctable *pr; + struct proctable *pr_findpid(); + + + /* Lookup process in table. Return ERR if there is no entry. + */ + if ((pr = pr_findpid (pid)) == NULL) + return (ERR); + else { + *inchan = pr->pr_inchan; + *outchan = pr->pr_outchan; + return (pid); + } +} + + +/* PR_FINDPID -- Search the process table for a process. NULL is returned if + * the process cannot be found, otherwise a pointer to the table entry is + * returned. + */ +struct proctable * +pr_findpid (int pid) +{ + register int pr; + + + for (pr=0; pr < MAXPROCS; pr++) { + if (prtable[pr].pr_pid == pid) + return (&prtable[pr]); + } + + return (NULL); +} + + +/* PR_RELEASE -- Release the table entry for the process. Used when a process + * is killed and we do not wish to wait for process termination. + */ +void +pr_release (int pid) +{ + register struct proctable *pr; + + if ((pr = pr_findpid (pid)) != NULL) + pr->pr_pid = (int) 0; +} diff --git a/unix/os/tape.c b/unix/os/tape.c new file mode 100644 index 00000000..d6677bcf --- /dev/null +++ b/unix/os/tape.c @@ -0,0 +1,508 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef sun +#include +#include +#include +#endif + + +/* + * TAPE.C -- Magtape test program (for most UNIX systems). + * + * Commands: + * + * open [device [r|w]] open device with given mode + * close close device + * rew[ind] rewind + * + * fsf [n] forward space N filemarks + * bsf [n] backspace N filemarks + * fsr [n] forward space N records + * bsr [n] backspace N records + * read [n [bufsize]] read N records + * write [n [bufsize]] write N records + * seek [offset[bkm]] seek (b=block#, k=x1024, m=x1024*1024) + * weof write end of file + * + * s[tatus] print tape status (device dependent) + * verbose toggle verbose status mode + * + * log [file] toggle logging to file (def:tape.out) + * run file execute commands in "file" + * ? or help print commands + * q[uit] exit command loop + * + * The drive to be opened is by default the most recently referenced drive, + * or the value of the variable TAPE, if defined in the user environment. + * + * Records are read into a default buffer size of 65535 bytes on reads if + * no buffer size is specified. The number of bytes read will be printed, + * along with the first 76 or so printable ascii characters at the head of + * the record, omitting all control codes. This is usually enough to identify + * the type of record. + * + * On writes, the data written is an ascii representation of the file and + * record number, i.e., "file M record N". If the buffer size is not given + * the last buffer size specified is used, initially 1024. + */ + +/* #define SUNOS41 */ + +#define SZ_COMMAND 512 +#define SZ_FNAME 256 +#define SZ_IOBUF 262144 +#define NREAD 64512 +#define NWRITE 1024 +#define EOS '\0' + +static char mtdev[SZ_FNAME]; +static char o_mtdev[SZ_FNAME]; +static char iobuf[SZ_IOBUF]; +static char cmdbuf[SZ_COMMAND]; +static char tokbuf[SZ_COMMAND]; +static char logfile[SZ_FNAME]; +static int rbufsz, wbufsz; +static int t_fileno; +static int t_blkno; +static int t_acmode; +static int verbose; +static int status; +extern int errno; +static FILE *stack[20]; +static FILE *logfp; +static char *tp; +static int tape; +static int sp; + +char *nextcmd(), *prompt(); +char *gettok(), *getenv(); + +void mtop (int op, int count); +char *nextcmd (FILE *in); +char *gettok (void); +char *prompt (void); +void pstatus (void); +void output (char *text); +void phelp (void); + + +/* TAPE program main. + */ +int +main (int argc, char *argv[]) +{ + char lbuf[256]; + int nrec, nbytes; + char *token; + FILE *in; + FILE *fp; + + + errno = 0; + t_blkno = 0; + t_fileno = 0; + rbufsz = NREAD; + wbufsz = NWRITE; + strcpy (logfile, "tape.out"); + + if (argc > 1) + strcpy (o_mtdev, argv[1]); + + for (in=stdin, sp=0; sp >= 0; ) { + /* Prompt if interactive. */ + if (in == stdin) { + fputs (prompt(), stdout); + fflush (stdout); + } + + /* Get command from current input stream. */ + if (nextcmd (in) == NULL) { +quit: if (in != stdin) + fclose (in); + if (--sp >= 0) + in = stack[sp]; + continue; + } else if (*tp == '!') { + system (tp+1); + continue; + } else if ((token = gettok()) == NULL) + continue; + + if (in == stdin) { + /* Log command if entered interactively. */ + if (logfp) + fputs (cmdbuf, logfp); + } else { + /* Echo command if noninteractive. */ + output (cmdbuf); + fflush (stdout); + } + + /* Check for program control commands. */ + if (!strncmp (token, "quit", 1)) { + goto quit; + } else if (!strcmp (token, "?") || !strcmp (token, "help")) { + phelp(); + continue; + } else if (!strncmp (token, "status", 2)) { + pstatus(); + continue; + } else if (!strncmp (token, "verbose", 3)) { + verbose = !verbose; + continue; + + } else if (!strncmp (token, "log", 3)) { + /* Ignore log commands not entered interactively. */ + if (in != stdin) + continue; + + /* Toggle logging. */ + if (logfp) { + printf ("logging disabled\n"); + fclose (logfp); + logfp = NULL; + } else { + if ( (token = gettok()) ) + strcpy (logfile, token); + if ((logfp = fopen (logfile, "a")) == NULL) + printf ("cannot open logfile %s\n", logfile); + else { + printf ("logging output to %s\n", logfile); + fprintf (logfp, "# --- BEGIN ---\n"); + } + } + continue; + + } else if (!strcmp (token, "run")) { + if (!(token = gettok()) || (fp=fopen(token,"r")) == NULL) + printf ("cannot run %s\n", token ? token : "?"); + else { + stack[sp++] = in; + in = fp; + } + continue; + } + + /* + * TAPE CONTROL commands. + */ + + if (!strncmp (token, "open", 1)) { + /* Get device name. */ + if (!(token=gettok()) || !strcmp (token, ".")) { + if (!o_mtdev[0] && (token = getenv ("TAPE"))) + strcpy (mtdev, token); + else + strcpy (mtdev, o_mtdev); + } else if (token[0]) + strcpy (mtdev, token); + + if (!mtdev[0]) { + output ("no tape device specified\n"); + continue; + } + + /* Open device. */ + if ((tape = open (mtdev, t_acmode = + ((token=gettok()) && *token == 'w') ? 2 : 0)) == -1) { + sprintf (lbuf, "cannot open device %s\n", mtdev); + output (lbuf); + mtdev[0] = EOS; + continue; + } + sprintf (lbuf, + "device %s open on descriptor %d\n", mtdev, tape); + output (lbuf); + strcpy (o_mtdev, mtdev); + } else if (!strncmp (token, "close", 1)) { + close (tape); + if (t_acmode) { + t_fileno++; + t_blkno = 0; + } + mtdev[0] = EOS; + errno = 0; + } else if (!strncmp (token, "rew", 3)) { + mtop (MTREW, 1); + t_fileno = 0; + t_blkno = 0; + errno = 0; + } else if (!strcmp (token, "weof")) { + mtop (MTWEOF, 1); + t_fileno++; + t_blkno = 0; + + } else if (!strcmp (token, "fsf")) { + mtop (MTFSF, (token = gettok()) ? atoi(token) : 1); + } else if (!strcmp (token, "fsr")) { + mtop (MTFSR, (token = gettok()) ? atoi(token) : 1); + } else if (!strcmp (token, "bsf")) { + mtop (MTBSF, (token = gettok()) ? atoi(token) : 1); + } else if (!strcmp (token, "bsr")) { + mtop (MTBSR, (token = gettok()) ? atoi(token) : 1); + + } else if (!strncmp (token, "read", 1)) { + register int i, j; + + nrec = (token = gettok()) ? atoi(token) : 1; + nbytes = rbufsz = (token = gettok()) ? atoi(token) : rbufsz; + + for (j=0; j < nrec; j++) { + for (i=0; i < nbytes; i++) + iobuf[i] = 0; + status = read (tape, iobuf, nbytes); + pstatus(); + + if (status < 0) { + output (" ERR\n"); + } else if (status == 0) { + output (" EOF\n"); + } else if (status > 0) { + char obuf[512]; + char *op, *ip, ch; + + op = obuf; *op++ = ' '; *op++ = ' '; + for (i=0, ip=iobuf; i < status && op-obuf < 78; i++) + if ((ch = ip[i]) > 040 && ch < 0177) + *op++ = ip[i]; + *op++ = '\n'; + *op++ = EOS; + output (obuf); + } + } + + continue; + + } else if (!strncmp (token, "write", 1)) { + register int i; + + nrec = (token = gettok()) ? atoi(token) : 1; + nbytes = wbufsz = (token = gettok()) ? atoi(token) : wbufsz; + if (nbytes > SZ_IOBUF) + nbytes = SZ_IOBUF; + + for (i=0; i < nbytes; i++) + iobuf[i] = 0; + + for (i=0; i < nrec; i++) { + sprintf (iobuf, "file %d, record %d\n", + t_fileno, t_blkno); + status = write (tape, iobuf, nbytes); + t_blkno++; + pstatus(); + } + + continue; + + } else if (!strncmp (token, "seek", 2)) { + char *ip; + int fwd, bak, i; + + if ( (token = gettok()) ) { + ip = token; + fwd = bak = 0; + if (*ip == '-') { + bak++; + ip++; + } else if (*ip == '+') { + fwd++; + ip++; + } + + for (i=0; isdigit(*ip); ip++) + i = i * 10 + (*ip - '0'); + + switch (*ip) { + case 'b': + i *= rbufsz; + break; + case 'k': + i *= 1024; + break; + case 'm': + i *= (1024*1024); + break; + } + + if (fwd) + status = lseek (tape, (off_t)i, 1); + else if (bak) + status = lseek (tape, -(off_t)i, 1); + else + status = lseek (tape, (off_t)i, 0); + pstatus(); + + } else { + status = lseek (tape, 0, 1); + pstatus(); + } + + } else + output ("unrecognized command\n"); + + if (verbose) + pstatus(); + fflush (stdout); + } + + return (0); +} + + +/* MTOP -- Execute a magtape operation. + */ +void +mtop ( + int op, /* operation code */ + int count /* count argument */ +) +{ + struct mtop mt; + + mt.mt_op = op; + mt.mt_count = count; + status = ioctl (tape, MTIOCTOP, &mt); + if (!verbose && status < 0) + pstatus(); +} + + +/* NEXTCMD -- Get next command. + */ +char * +nextcmd (FILE *in) +{ + fflush (stdout); + if (fgets (cmdbuf, SZ_COMMAND, in) == NULL) + return (NULL); + else + return (tp = cmdbuf); +} + + +/* GETTOK -- Get next token from the input stream. + */ +char * +gettok (void) +{ + register char *op; + register int ch; + + while (*tp && isspace(*tp)) + tp++; + if (*tp == EOS || *tp == '#') + return (NULL); + + for (op=tokbuf; (ch = *tp) && !isspace(ch); tp++, op++) + *op = ch; + + *op = EOS; + return (tokbuf); +} + + +/* PROMPT -- Return a pointer to the prompt string. + */ +char * +prompt (void) +{ + static char prompt[32]; + static char defp[] = "% "; + register char *ip, *dev; + + for (ip=dev=mtdev; *ip; ip++) + if (*ip == '/') + dev = ip + 1; + + if (*dev) { + sprintf (prompt, "(%s) ", dev); + return (prompt); + } else + return (defp); +} + + +/* PSTATUS -- Print status of tape and last operation. + */ +void +pstatus (void) +{ + char obuf[512]; + + +#ifdef sun + static struct mt_tape_info info[] = MT_TAPE_INFO; + struct mt_tape_info *tp; + struct mtget mt; + char *tn; + + if (verbose) { + if (ioctl (tape, MTIOCGET, &mt) != 0) + sprintf (obuf, "MTIOCGET ioctl fails\n"); + else { + for (tn="unknown", tp=info; tp->t_type; tp++) + if (tp->t_type == mt.mt_type) { + tn = tp->t_name; + break; + } + + sprintf (obuf, + "status %d (%d) file=%d block=%d resid=%d [ds=0x%x er=0x%x] %s\n", + status, errno, mt.mt_fileno, mt.mt_blkno, + mt.mt_resid, mt.mt_dsreg, mt.mt_erreg, tn); + } + } else + sprintf (obuf, "status %d (%d)\n", status, errno); +#else + sprintf (obuf, "status %d (%d)\n", status, errno); +#endif + + output (obuf); + fflush (stdout); +} + + +/* OUTPUT -- Write text to the standard output, and to the logfile output + * if enabled. + */ +void +output (char *text) +{ + fputs (text, stdout); + if (logfp) { + fputs ("# ", logfp); + fputs (text, logfp); + } +} + + +char *helptxt[] = { + "Usage: tape [device]. The following commands are provided:\n", + "\n", + " open [device [r|w]] rewind fsf [n]\n", + " close read [nrec [bufsz]] fsr [n]\n", + " log [file] write [nrec [bufsz]] bsf [n]\n", + " run weof bsr [n]\n", + " verbose status quit\n", + 0 }; + +/* PHELP -- Print list of commands. + */ +void +phelp (void) +{ + register int i; + + for (i=0; helptxt[i]; i++) + output (helptxt[i]); +} diff --git a/unix/os/zalloc.c b/unix/os/zalloc.c new file mode 100644 index 00000000..4cc19765 --- /dev/null +++ b/unix/os/zalloc.c @@ -0,0 +1,206 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include + +#define import_spp +#define import_alloc +#define import_kernel +#define import_knames +#include + +/* + * ZALLOC.C -- Device allocation interface. Requires the dev$devices table, + * which is read by the high level code before we are called. + * + * zdvall (device, allflg, status) allocate/deallocate device + * zdvown (device, owner, maxch, status) query device allocation + * + * Status returns (import_alloc): + * + * OK operation successful + * ERR cannot allocate device + * DV_DEVALLOC device is already allocated + * DV_DEVINUSE device is in use by someone else + * DV_DEVFREE device is free and can be allocated + * + * The DEVICE name is a list of aliases for the physical device. On UNIX there + * can be multiple elements in the list (e.g., for a tape drive), but on other + * systems there may never be more than one device name. The elements of the + * list are delimited by whitespace. On host systems that do not support + * multiple device aliases, the kernel may assume that DEVICE is a scalar. + */ + +#define ALLOCEXE "alloc.e" + +static int u_allocstat (char *aliases); + + + +/* ZDVALL -- Allocate or deallocate a device. UNIX does not explicitly support + * device allocation, so we fake it by setting the device owner and removing + * group and other permissions. This requires superuser privilege, hence a + * separate process HLIB$ALLOC.E is used to set/remove device allocation. + */ +int +ZDVALL ( + PKCHAR *aliases, /* list of aliases for device */ + XINT *allflg, /* allocate or deallocate device? */ + XINT *status /* receives status word */ +) +{ + PKCHAR cmd[SZ_LINE+1], nullstr[1]; + + extern int ZOSCMD (); + + + /* Syntax: $host/hlib/alloc.e -[ad] aliases + */ + strcpy ((char *)cmd, irafpath(ALLOCEXE)); + strcat ((char *)cmd, *allflg ? " -a " : " -d "); + strcat ((char *)cmd, (char *)aliases); + + *nullstr = XEOS; + (void) ZOSCMD (cmd, nullstr, nullstr, nullstr, status); + if (*status == DV_ERROR) + *status = XERR; + + return (*status); +} + + +/* ZDVOWN -- Query device allocation. Tells whether or not the named device + * is allocated, and if so returns the "name" of the owner in the owner + * string. Just what the "name" string is is not precisely defined, it is + * merely printed for the user to tell them the status of the device. + * Note that the device is not considered to be allocated if the owner + * is not currently logged in. + * + * Device files may be specified by a full pathname, as a user directory + * relative pathname, or by the device name in /dev or /dev/rmt. + */ +int +ZDVOWN ( + PKCHAR *device, /* device name (not a list) */ + PKCHAR *owner, /* receives owner string */ + XINT *maxch, /* max chars out */ + XINT *status /* receives allocation status */ +) +{ + register int uid; + char *dev, devname[SZ_FNAME+1]; + struct passwd *pw, *getpwuid(); + struct stat fi; + + + /* Get device pathname. */ + dev = (char *)device; + if (dev[0] == '/') { + strcpy (devname, dev); + + } else if (dev[0] == '~' && dev[1] == '/') { + /* User home directory relative pathname. */ + struct passwd *pwd; + pwd = getpwuid (getuid()); + if (pwd != NULL) { + strcpy (devname, pwd->pw_dir); + strcat (devname, &dev[1]); + endpwent(); + } + } else { + sprintf (devname, "/dev/%s", dev); + if (access (devname, 0) == ERR) + sprintf (devname, "/dev/rmt/%s", dev); + } + + if (stat (devname, &fi) == ERR) { + *status = XERR; + return (XERR); + } + + uid = fi.st_uid; + *owner = XEOS; + + if (uid == 0) + *status = DV_DEVFREE; + else if (uid == getuid()) + *status = DV_DEVALLOC; + /* else if (!loggedin (uid)) */ + else if (u_allocstat ((char *)device) == DV_DEVFREE) + *status = DV_DEVFREE; + else { + if ((pw = getpwuid (uid)) == NULL) + sprintf ((char *)owner, "%d", uid); + else + strncpy ((char *)owner, pw->pw_name, *maxch); + *status = DV_DEVINUSE; + } + + return (*status); +} + + +/* LOGGEDIN -- Return 1 if uid is logged in, else 0. + */ +int +loggedin (int uid) +{ + struct utmpx ubuf; + struct passwd *pw, *getpwuid(); + FILE *ufp; + + if ((ufp = fopen ("/var/run/utmp", "r")) == NULL) { + printf ("zdvown: cannot open /var/run/utmp\n"); + return (1); + } + + if ((pw = getpwuid (uid)) == NULL) { + fclose (ufp); + return (0); + } + + do { + if (fread (&ubuf, sizeof (struct utmpx), 1, ufp) == (size_t) 0) { + fclose (ufp); + return (0); + } + } while (strncmp (ubuf.ut_user, pw->pw_name, 8) != 0); + + fclose (ufp); + + return (1); +} + + +/* U_ALLOCSTAT -- Call alloc.e to get the device status. Currently, this has + * to be done by a priviledged process as the process table is used in some + * cases. + */ +static int +u_allocstat ( + char *aliases /* list of aliases for device */ +) +{ + PKCHAR cmd[SZ_LINE+1], nullstr[1]; + XINT x_status; + + extern int ZOSCMD(); + + + /* Syntax: $host/hlib/alloc.e -s aliases + */ + strcpy ((char *)cmd, irafpath(ALLOCEXE)); + strcat ((char *)cmd, " -s "); + strcat ((char *)cmd, aliases); + + *nullstr = XEOS; + (void) ZOSCMD (cmd, nullstr, nullstr, nullstr, &x_status); + if (x_status == DV_ERROR) + x_status = XERR; + + return (x_status); +} diff --git a/unix/os/zawset.c b/unix/os/zawset.c new file mode 100644 index 00000000..62b0ef6b --- /dev/null +++ b/unix/os/zawset.c @@ -0,0 +1,154 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#ifdef SYSV +#define NORLIMIT +#endif + +#ifndef CYGWIN +#ifdef LINUX +#undef NORLIMIT +#endif +#endif + +#ifdef SOLARIS +#define RLIMIT_RSS RLIMIT_VMEM +#undef NORLIMIT +#endif + +#include +#include +#ifndef NORLIMIT +#include +#include +#endif + +#define import_kernel +#define import_knames +#define import_spp +#include + +#define PCT_RESERVE 10 /* percent */ +#define MIN_RESERVE 50 /* megabytes */ +#define MIN_WORKSET 32 /* megabytes */ + +#define ENV_DEBUG "ZAWSET_DEBUG" +#define MB (1024*1024) +#define KB 1024 + +/* Kernel default working set values in bytes. */ +unsigned int defworkset = SZ_DEFWORKSET; +unsigned int maxworkset = SZ_MAXWORKSET; +static unsigned int max_wss = 0; +extern char *getenv(); + + +/* ZAWSET -- Adjust or query the "working set", i.e., the maximum amount of + * physical memory allocated to the process. + */ +int +ZAWSET ( + XINT *best_size, /* requested working set size, bytes. */ + XINT *new_size, + XINT *old_size, /* actual new and old sizes, bytes. */ + XINT *max_size /* max working set size, bytes */ +) +{ + int physmem=0, kb_page; + int debug = (getenv(ENV_DEBUG) != NULL); + char *s, *getenv(); + +#ifndef NORLIMIT + unsigned int working_set_size; + struct rlimit rlp; +#endif + + + /* Get the page size in kilobytes. */ + kb_page = getpagesize() / KB; + +#ifdef _SC_PHYS_PAGES + /* On recent POSIX systems (including Solaris, Linux, and maybe + * others) we can use sysconf to get the actual system memory size. + * The computation is done in KB to avoid integer overflow. + */ + physmem = sysconf(_SC_PHYS_PAGES) * kb_page; + if (physmem > 0) { + maxworkset = min (MAX_LONG / KB, physmem); + + /* Don't try to use all of physical memory. */ + if (maxworkset == physmem) { + maxworkset -= (max ((MIN_RESERVE*MB)/KB, + physmem * PCT_RESERVE / 100)); + if (maxworkset <= 0) + maxworkset = (MIN_WORKSET * MB) / KB; + } + + /* Now convert back to bytes. */ + maxworkset *= 1024; + } +#endif + + /* The hard upper limit on memory utilization defined by the unix + * kernel can be limited either by the value compiled into the IRAF + * kernel, or by the value set in the user environment variable + * MAXWORKSET, given in units of Mb. + */ + if (!max_wss) { + if ( (s = getenv ("MAXWORKSET")) ) { + max_wss = atoi(s) * 1024*1024; + if (max_wss < 1024*1024) + max_wss = maxworkset; + } else + max_wss = maxworkset; + } + + if (debug) + fprintf(stderr,"zawset: physmem=%dm, maxworkset=%dm max_wss=%dm\n", + physmem / KB, maxworkset / MB, max_wss / MB); + +#ifdef NORLIMIT + if (*best_size == 0) + *old_size = *new_size = defworkset; + else + *new_size = *old_size = min (max_wss, *best_size); + *max_size = max_wss; +#else + getrlimit (RLIMIT_RSS, &rlp); + if (debug) + fprintf (stderr, "zawset: starting rlimit cur=%ldm, max=%ldm\n", + (long)(rlp.rlim_cur == RLIM_INFINITY ? 0 : rlp.rlim_cur) / MB, + (long)(rlp.rlim_max == RLIM_INFINITY ? 0 : rlp.rlim_max) / MB); + + working_set_size = min (max_wss, + rlp.rlim_cur == RLIM_INFINITY ? max_wss : rlp.rlim_cur); + + /* Now try to set the size requested by our caller. If bestsize was + * given as zero, merely return the status values. + */ + (*max_size) = min (max_wss, + rlp.rlim_max == RLIM_INFINITY ? max_wss : rlp.rlim_max); + + if (*best_size <= 0) + *new_size = *old_size = working_set_size; + else { + rlp.rlim_cur = min (*best_size, *max_size); + if (rlp.rlim_cur > working_set_size) + setrlimit (RLIMIT_RSS, &rlp); + getrlimit (RLIMIT_RSS, &rlp); + *old_size = working_set_size; + *new_size = min(*best_size, min(max_wss, + rlp.rlim_cur == RLIM_INFINITY ? max_wss : rlp.rlim_cur)); + } + if (debug) + fprintf (stderr, "zawset: adjusted rlimit cur=%ldm, max=%ldm\n", + (long)(rlp.rlim_cur == RLIM_INFINITY ? 0 : rlp.rlim_cur) / MB, + (long)(rlp.rlim_max == RLIM_INFINITY ? 0 : rlp.rlim_max) / MB); +#endif + if (debug) + fprintf (stderr, "zawset: best=%ldm old=%ldm new=%ldm max=%ldm\n", + (long)*best_size/MB, (long)*old_size/MB, + (long)*new_size/MB, (long)*max_size/MB); + + return (XOK); +} diff --git a/unix/os/zcall.c b/unix/os/zcall.c new file mode 100644 index 00000000..18d28ee4 --- /dev/null +++ b/unix/os/zcall.c @@ -0,0 +1,91 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include + +#define import_spp +#define import_kernel +#define import_knames +#include + +/* ZCALL[0-10] -- Call the procedure whose entry point address is pointed to + * by the first argument, which is the integer valued entry point address of + * the procedure as returned by ZLOCPR. Up to ten arguments are passed by + * reference to the called subprocedure. + */ + + + + +int ZCALL0 (XINT *proc) +{ + return (*(PFI)(*proc))(); +} + +int ZCALL1 (XINT *proc, void *arg1) +{ + return (*(PFI)(*proc)) (arg1); +} + + +int ZCALL2 (XINT *proc, void *arg1, void *arg2) +{ + return (*(PFI)(*proc)) (arg1, arg2); +} + + +int ZCALL3 (XINT *proc, void *arg1, void *arg2, void *arg3) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3); +} + + +int ZCALL4 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4); +} + + +int ZCALL5 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5); +} + + +int ZCALL6 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5, arg6); +} + + +int ZCALL7 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6, void *arg7) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5, arg6, arg7); +} + + +int ZCALL8 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6, void *arg7, void *arg8) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); +} + + +int ZCALL9 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6, void *arg7, void *arg8, void *arg9) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9); +} + + +int ZCALLA (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6, void *arg7, void *arg8, void *arg9, + void *arg10) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10); +} diff --git a/unix/os/zdojmp.c b/unix/os/zdojmp.c new file mode 100644 index 00000000..2b825cde --- /dev/null +++ b/unix/os/zdojmp.c @@ -0,0 +1,38 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include + +#include +#define import_spp +#define import_kernel +#define import_knames +#include + +/* ZDOJMP -- Restore the saved processor context (non-local goto). See also + * as$zsvjmp.s, where most of the work is done. + */ +void +ZDOJMP (XINT *jmpbuf, XINT *status) +{ +#ifdef DOJMP_ORIG + register int stat = *status ? *status : 1; + register long *jb = (long *)jmpbuf; + + *((int *)jb[0]) = stat; + longjmp (&jb[1], stat); + +#else + register int stat = *status ? *status : 1; + register XINT *status_ptr = ((XINT **)jmpbuf)[0]; + register void *jb = (XINT **)jmpbuf+1; + + *status_ptr = stat; +#if (defined(LINUX) || defined(CYGWIN)) + siglongjmp (jb, stat); +#else + longjmp (jb, stat); +#endif + +#endif +} diff --git a/unix/os/zfacss.c b/unix/os/zfacss.c new file mode 100644 index 00000000..f6624698 --- /dev/null +++ b/unix/os/zfacss.c @@ -0,0 +1,124 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + +#define SZ_TESTBLOCK 1024 /* for TEXT/BINARY heuristic */ +#define MAX_LINELEN 256 /* when looking for newlines */ +#define R 04 /* UNIX access() codes */ +#define W 02 +#define ctrlcode(c) ((c) >= '\007' && (c) <= '\017') + + +/* ZFACSS -- Try to determine if FILE is accessible in the indicated MODE. + * If file is accessible for reading and TYPE is given as TEXT_FILE, + * look at the first block of data to see if it is legal text data. + * ACCESS(file,0,0) merely checks that the file exists. Any file is a + * legal binary file. + */ +int +ZFACSS ( + PKCHAR *fname, + XINT *mode, + XINT *type, + XINT *status +) +{ + static char modebits[] = { 0, R, R|W, W, R|W }; + register char *ip, ch; + register int n; + char buf[SZ_TESTBLOCK]; + int fd, acmode, accessible, nchars, newline_seen; + struct stat fi; + + /* Null filename? */ + if (*(char *)fname == EOS) { + *status = NO; + return (NO); + } + + /* Map IRAF access mode into UNIX access mode. + */ + if (*mode >= READ_ONLY && *mode <= APPEND) + acmode = modebits[*mode]; + else if (*mode == 0 && *type != 0) + acmode = R; + else + acmode = 0; + + /* Is file accessible with the given mode. + */ + accessible = (access ((char *)fname, acmode) == 0); + + if (accessible && *type == DIRECTORY_FILE) { + stat ((char *)fname, &fi); + if (fi.st_mode & S_IFDIR) + *status = YES; + else + *status = NO; + return (*status); + + } else if (!accessible && *type == SYMLINK_FILE) { + lstat ((char *)fname, &fi); + if (fi.st_mode & S_IFLNK) + *status = YES; + else + *status = NO; + + return (*status); + } + + /* If we have to check the file type (text or binary), then we must + * actually look at some file data since UNIX does not discriminate + * between text and binary files. NOTE that this heuristic is not + * completely reliable and can fail, although in practice it does + * very well. + */ + if (accessible && (acmode & R) && *type != 0) { + stat ((char *)fname, &fi); + + /* Do NOT read from a special device (may block) */ + if ((fi.st_mode & S_IFMT) & S_IFREG) { + /* If we are testing for a text file the portion of the file + * tested must consist of only printable ascii characters or + * whitespace, with occasional newline line delimiters. + * Control characters embedded in the text will cause the + * heuristic to fail. We require newlines to be present in + * the text to disinguish the case of a binary file containing + * only ascii data, e.g., a cardimage file. + */ + fd = open ((char *)fname, 0); + if (fd >= 0 && (nchars = read (fd, buf, SZ_TESTBLOCK)) > 0) { + ip = buf; + for (n=nchars, newline_seen=0; --n >= 0; ) { + ch = *ip++; + if (ch == '\n') + newline_seen++; + else if (!isprint(ch) && !isspace(ch) && !ctrlcode(ch)) + break; + } + + if (*type == TEXT_FILE) { + if (n >= 0 || (nchars > MAX_LINELEN && !newline_seen)) + accessible = NO; + } else if (*type == BINARY_FILE && n < 0) + accessible = NO; + close (fd); + } + } else if (fi.st_mode & S_IFCHR && *type != TEXT_FILE) + accessible = NO; + } + + (*status) = accessible; + + return (*status); +} diff --git a/unix/os/zfaloc.c b/unix/os/zfaloc.c new file mode 100644 index 00000000..c0eb09cd --- /dev/null +++ b/unix/os/zfaloc.c @@ -0,0 +1,104 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZFALOC -- Preallocate space for a large file of known size, without having + * to physically write zero blocks. In UNIX this is done by seeking to the + * desired end of file and writing some data. Standard UNIX does not provide + * any way to allocate a contiguous or near-contiguous file. + */ +int +ZFALOC ( + PKCHAR *fname, + XLONG *nbytes, + XINT *status +) +{ + char data = 0; + char *s; + int fd; + off_t lseek(); + extern char *getenv(); + extern int _u_fmode(); + + + if ((fd = creat ((char *)fname, _u_fmode(FILE_MODEBITS))) == ERR) { + *status = XERR; + return (XERR); + } + + /* Fix size of file by seeking to the end of file minus one byte, + * and writing one byte of data. UNIX will not allocate the remaining + * fileblocks until they are written into at run time; when referenced + * the blocks will be zero-fill on demand. + */ + if (*nbytes > 0) { + if (lseek (fd, (off_t)(*nbytes - 1), 0) == ERR) { + close (fd); + *status = XERR; + return (XERR); + } + if (write (fd, &data, 1) == ERR) { + close (fd); + *status = XERR; + return (XERR); + } + lseek (fd, (off_t)0, 0); + } + + /* For efficiency reasons the above is all we normally do. However, + * if ZFALOC is set in the environment we touch each file block at + * least once in order to preallocate all the space at zfaloc time. + * ZFALOC may optionally have a string value. If no value is given + * all files are match (zfaloc is fully allocate all files). + * Otherwise, the string value is a comma delimited list of simple + * pattern strings. A file is matched, and space preallocated, if + * the given substring appears anywhere in the file name. + */ + if ( (s = getenv ("ZFALOC")) ) { + register char *ip, *op; + char patstr[SZ_PATHNAME]; + int match = (*s == '\0'); + int patlen, i; + + while (!match && *s) { + for (op=patstr; *s && *s != ','; ) + *op++ = *s++; + *op = '\0'; + patlen = strlen (patstr); + if (*s == ',') + s++; + + for (ip=(char *)fname; *ip; ip++) + if (*ip == patstr[0] && !strncmp(ip,patstr,patlen)) { + match++; + break; + } + } + + if (match) + for (i=0; i < *nbytes; i += 512) { + lseek (fd, (off_t)i, 0); + if (write (fd, &data, 1) < 0) { + *status = XERR; + close (fd); + return (XERR); + } + } + } + + close (fd); + *status = XOK; + + return (XOK); +} diff --git a/unix/os/zfchdr.c b/unix/os/zfchdr.c new file mode 100644 index 00000000..3beae679 --- /dev/null +++ b/unix/os/zfchdr.c @@ -0,0 +1,57 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +extern char oscwd[]; + + +/* ZFCHDR -- Change the current working directory. Save directory name, + * excluding the trailing "/", in oscwd so that a subsequent call to ZFGCWD + * will be able to return directory name without a big hassle. + */ +int +ZFCHDR ( + PKCHAR *newdir, + XINT *status +) +{ + register char *ip, *op; + char dirname[SZ_PATHNAME]; + + + /* Change pathnames like "a/b/c/" to "a/b/c". + */ + for (ip=(char *)newdir, op=dirname; (*op = *ip++) != EOS; op++) + ; + if ((*(op-1) == '/') && (op - dirname > 1)) + *(op-1) = EOS; + + /* Ask UNIX to change the cwd to newdir. + */ + if (chdir (dirname) == ERR) { + *status = XERR; + + } else if (dirname[0] == '/') { + /* Save pathname of directory. + */ + strcpy (oscwd, dirname); + *status = XOK; + + } else { + /* Concatenate subdir name to current directory pathname. + */ + for (op=oscwd; *op; op++) + ; + if (*(op-1) != '/') + *op++ = '/'; + for (ip=dirname; (*op++ = *ip++); ) + ; + } + + return (*status); +} diff --git a/unix/os/zfdele.c b/unix/os/zfdele.c new file mode 100644 index 00000000..1e973040 --- /dev/null +++ b/unix/os/zfdele.c @@ -0,0 +1,27 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZFDELE -- Delete a file. + */ +int +ZFDELE ( + PKCHAR *fname, + XINT *status +) +{ + extern int vm_delete(); + + vm_delete ((char *)fname, 0); + if (unlink ((char *)fname) == ERR) + *status = XERR; + else + *status = XOK; + + return (*status); +} diff --git a/unix/os/zfgcwd.c b/unix/os/zfgcwd.c new file mode 100644 index 00000000..29b476c0 --- /dev/null +++ b/unix/os/zfgcwd.c @@ -0,0 +1,65 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include + +#define import_spp +#define import_kernel +#define import_knames +#include + +extern char oscwd[]; + + +/* ZFGCWD -- Get working (current) UNIX directory. The current working + * directory, once set, is saved in oscwd. + */ +int +ZFGCWD ( + PKCHAR *outstr, + XINT *maxch, + XINT *status +) +{ + register char *ip, *op; + register int n; + char dirname[1025]; +#ifdef POSIX + char *getcwd(); +#else + char *getwd(); +#endif + + /* If cwd is already known, just return the name. Reconstructing + * the pathname of the cwd is expensive on some systems. + */ + if (oscwd[0] != EOS) + ip = oscwd; + else { +#ifdef POSIX + ip = getcwd (dirname, 1024); +#else + ip = getwd (dirname); +#endif + if (ip == NULL) { + *status = XERR; + return (XERR); + } else + strcpy (oscwd, dirname); + } + + op = (char *)outstr; + for (n = *maxch; --n >= 0 && (*op = *ip++) != EOS; ) + op++; + + /* Make sure a concatenatable directory prefix is returned. + */ + if (*(op-1) != '/') { + *op++ = '/'; + *op = EOS; + } + + *status = op - (char *)outstr; + + return (*status); +} diff --git a/unix/os/zfinfo.c b/unix/os/zfinfo.c new file mode 100644 index 00000000..db5803fb --- /dev/null +++ b/unix/os/zfinfo.c @@ -0,0 +1,99 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_spp +#define import_finfo +#include + +/* ZFINFO -- Get information describing the named file. Access times + * are returned in units of seconds since 00:00:00 01-Jan-80, local time. + */ +int +ZFINFO ( + PKCHAR *fname, + XLONG *finfo_struct, + XINT *status +) +{ + struct stat osfile; + struct _finfo *fs; + struct passwd *getpwuid(); + time_t gmt_to_lst(); + int stat(); + + /* Get UNIX file info. + */ + fs = (struct _finfo *)finfo_struct; + if (stat ((char *)fname, &osfile) == ERR) { + *status = XERR; + return (XERR); + } + + /* Determine file type. + */ + if (osfile.st_mode & S_IFDIR) + fs->fi_type = FI_DIRECTORY; + else if (osfile.st_mode & S_IEXEC) + fs->fi_type = FI_EXECUTABLE; + else if (osfile.st_mode & S_IFREG) + fs->fi_type = FI_REGULAR; + else + fs->fi_type = FI_SPECIAL; + + /* Set file size (in bytes), access times, and file permission bits. + * Times must be converted from GMT epoch 1970 to local standard time, + * epoch 1980. + */ + fs->fi_size = osfile.st_size; + fs->fi_atime = gmt_to_lst (osfile.st_atime); + fs->fi_mtime = gmt_to_lst (osfile.st_mtime); + fs->fi_ctime = gmt_to_lst (osfile.st_ctime); + + /* Encode file access permission bits. + */ + { + static int osbits[] = { 0400, 0200, 040, 020, 04, 02 }; + int bit; + + for (bit=0, fs->fi_perm=0; bit < 6; bit++) + fs->fi_perm |= (osfile.st_mode & osbits[bit]) ? 1<fi_owner, owner, SZ_OWNERSTR); + else { + setpwent(); + pw = getpwuid (osfile.st_uid); + endpwent(); + + if (pw == NULL) + sprintf ((char *)fs->fi_owner, "%d", osfile.st_uid); + else { + strncpy (owner, pw->pw_name, SZ_OWNERSTR); + strncpy ((char *)fs->fi_owner, owner, SZ_OWNERSTR); + uid = osfile.st_uid; + } + } + ((char *)fs->fi_owner)[SZ_OWNERSTR] = EOS; + } + + *status = XOK; + + return (*status); +} diff --git a/unix/os/zfiobf.c b/unix/os/zfiobf.c new file mode 100644 index 00000000..d3fd18ff --- /dev/null +++ b/unix/os/zfiobf.c @@ -0,0 +1,888 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include + +# ifndef O_NDELAY +#include +# endif + +#include +#include + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_spp +#include + +/* + * ZFIOBF -- FIO interface to UNIX 4.1BSD binary files. + * This is the interface to general, random access disk resident binary + * files (as opposed to text files). The basic strategy is very simple. + * FIO will request an asynchronous read or write of N device blocks at + * a given offset. The offset (one-indexed) is guaranteed by FIO to be + * aligned on a device block boundary, and to be in bounds. The size of + * a device block and of a file are determined at open time by FIO, which + * calls the zsttbf status routine. + * + * FIO ASSUMES that it can extend a file by writing at EOF in an ordinary + * zawrbf call with the appropriate one-indexed byte offset. If the last + * block in the file is a partial block, FIO will write at some device + * block offset within the file, after first reading the partial block + * into the FIO buffer. FIO will never write a partial block within + * a file, but assumes it can do so at the end of the file. If the OS + * does not support irregular length files, the interface routines should + * simulate it somehow. The FIO buffer is an integral number of SPP chars + * in size, and read requests (in units of bytes) will always be for an integral + * number of chars. + * + * In UNIX 4.1BSD, there is no such thing as asynchronous i/o, so we have + * to fake it. Also, the UNIX i/o interface is sequential/seek, while the + * FIO interface is absolute offset, so we have to keep track of the file + * position to avoid a seek on every i/o access. + */ + +int _u_fmode (int mode); +int vm_access (char *fname, int mode); +int vm_reservespace (long nbytes); +int vm_largefile (long nbytes); +int vm_directio (int fd, int flag); + + +/* ZOPNBF -- Open a binary file. The file must exist for modes RO, WO, RW. + * A new file will always be created for mode NF, and a file will be created + * if it does not exist for mode AP. Append mode is write-only at EOF. + * It is also legal to open RW and append by seeking to EOF and writing, + * if more generality is required. + */ +int +ZOPNBF ( + PKCHAR *osfn, /* UNIX name of file */ + XINT *mode, /* file access mode */ + XINT *chan /* file number (output) */ +) +{ + register int fd; + struct stat filstat; + + /* Open or create file with given access mode. + */ + switch (*mode) { + case READ_ONLY: + /* The O_NDELAY is necessary for some types of special devices, + * e.g., a FIFO, and should be harmless for other file types. + */ + if ((fd = open ((char *)osfn, O_RDONLY|O_NDELAY)) != ERR) + fcntl (fd, F_SETFL, O_RDONLY); + break; + case WRITE_ONLY: + if ((fd = open ((char *)osfn, O_WRONLY|O_NDELAY)) != ERR) + fcntl (fd, F_SETFL, O_WRONLY); + break; + + case READ_WRITE: + fd = open ((char *)osfn, O_RDWR); + break; + + case NEW_FILE: + /* Create file and then reopen for read-write access. + */ + if ((fd = creat ((char *)osfn, _u_fmode(FILE_MODEBITS))) != ERR) { + close (fd); + fd = open ((char *)osfn, 2); + } + break; + + case APPEND: + /* It is legal to append to a nonexistent file. We merely create + * a new, zero length file and append to it. Read access is + * required on a binary file opened for appending, since FIO has + * to read the partial block at the end of the file before it can + * append to it. + */ + if (access ((char *)osfn, 0) == ERR) + close (creat ((char *)osfn, _u_fmode(FILE_MODEBITS))); + fd = open ((char *)osfn, 2); + break; + + default: + fd = ERR; + } + + /* Initialize the kernel file descriptor. Seeks are illegal if the + * device is a character special device; the device is a "streaming" + * file (blksize=1) if it can only be accessed sequentially. + */ + if (fd != ERR && stat ((char *)osfn, &filstat) == ERR) { + close (fd); + fd = ERR; + } + + /* Don't set *chan until we have successfully finished opening the + * file, otherwise any error occuring during the open will try to + * close the partially opened file. + */ + if (fd == ERR) { + *chan = XERR; + } else if (fd >= MAXOFILES) { + close (fd); + if (*mode == NEW_FILE) + unlink ((char *)osfn); + *chan = XERR; + } else { + zfd[fd].fp = NULL; + zfd[fd].fpos = 0L; + zfd[fd].nbytes = 0; + zfd[fd].flags = (filstat.st_mode & S_IFCHR) ? KF_NOSEEK : 0; + zfd[fd].filesize = filstat.st_size; + if (!vm_access ((char *)osfn, *mode)) + zfd[fd].flags |= KF_DIRECTIO; + *chan = fd; + } + + return (*chan); +} + + +/* ZCLSBF -- Close a binary file. + */ +int +ZCLSBF (XINT *fd, XINT *status) +{ + extern int errno; + + /* This is a bit of a kludge, but closing a FIFO pipe opened for + * reading (probably attempting the close before the writer has + * closed the connection) causes an EPERM error on the close. + * This is harmless and only causes the VOS task to report an + * error, so ignore the error. + */ + if ((*status = (close (*fd) == ERR) ? XERR : XOK) == XERR) + if (errno == EPERM) + *status = XOK; + + return (*status); +} + + +/* ZARDBF -- "Asynchronous" binary block read. Initiate a read of at most + * maxbytes bytes from the file FD into the buffer BUF. Status is returned + * in a subsequent call to ZAWTBF. + */ +int +ZARDBF ( + XINT *chan, /* UNIX file number */ + XCHAR *buf, /* output buffer */ + XINT *maxbytes, /* max bytes to read */ + XLONG *offset /* 1-indexed file offset to read at */ +) +{ + register struct fiodes *kfp; + register int fd; + off_t fileoffset; + int aligned; + off_t lseek(); + + fd = *chan; + kfp = &zfd[fd]; + fileoffset = *offset - 1L; + + /* If reading from a device on which seeks are illegal, offset should + * be zero (as when called by ZARDCL). Otherwise, we must seek to + * the desired position. + */ + if (*offset > 0 && kfp->fpos != fileoffset) { + if ((kfp->fpos = lseek(fd,fileoffset,0)) == ERR) { + kfp->nbytes = ERR; + return (XERR); + } + } + + /* Disable direct i/o if transfers are not block aligned. */ + aligned = (!(fileoffset % SZ_DISKBLOCK) && !(*maxbytes % SZ_DISKBLOCK)); + if ((kfp->flags & KF_DIRECTIO) && !aligned) + kfp->flags &= ~KF_DIRECTIO; + + if (kfp->flags & KF_DIRECTIO) + vm_directio (fd, 1); + + if ((kfp->nbytes = read (fd, (char *)buf, *maxbytes)) > 0) + kfp->fpos += kfp->nbytes; + + if (kfp->flags & KF_DIRECTIO && aligned) + vm_directio (fd, 0); + + return (XOK); +} + + +/* ZAWRBF -- "Asynchronous" binary block write. Initiate a write of exactly + * nbytes bytes from the buffer BUF to the file FD. Status is returned in a + * subsequent call to ZAWTBF. + */ +int +ZAWRBF ( + XINT *chan, /* UNIX file number */ + XCHAR *buf, /* buffer containing data */ + XINT *nbytes, /* nbytes to be written */ + XLONG *offset /* 1-indexed file offset */ +) +{ + register int fd; + register struct fiodes *kfp; + off_t fileoffset; + off_t lseek(); + int aligned; + + fd = *chan; + kfp = &zfd[fd]; + fileoffset = *offset - 1L; + + /* If writing to a device on which seeks are illegal, offset should + * be zero (as when called by ZAWRCL). Otherwise, we must seek to + * the desired position. + */ + if (*offset > 0 && kfp->fpos != fileoffset) + if ((kfp->fpos = lseek(fd,fileoffset,0)) == ERR) { + kfp->nbytes = ERR; + return (XERR); + } + + /* Disable direct i/o if transfers are not block aligned. */ + aligned = (!(fileoffset % SZ_DISKBLOCK) && !(*nbytes % SZ_DISKBLOCK)); + if ((kfp->flags & KF_DIRECTIO) && !aligned) + kfp->flags &= ~KF_DIRECTIO; + + if (kfp->flags & KF_DIRECTIO) { + vm_directio (fd, 1); + } else if (vm_largefile((long)offset) || vm_largefile((long)*nbytes)) { + /* Reserve VM space if writing at EOF. */ + struct stat st; + if (!fstat(fd,&st) && fileoffset >= st.st_size) + vm_reservespace (fileoffset + *nbytes - st.st_size); + } + + if ((kfp->nbytes = write (fd, (char *)buf, *nbytes)) > 0) + kfp->fpos += kfp->nbytes; + + if (kfp->flags & KF_DIRECTIO) + vm_directio (fd, 0); + + /* Invalidate cached file size, forcing a UNIX system call to determine + * the file size the next time ZSTTBF is called. + */ + kfp->filesize = -1; + + return (XOK); +} + + +/* ZAWTBF -- "Wait" for an "asynchronous" read or write to complete, and + * return the number of bytes read or written, or ERR. + */ +int +ZAWTBF (XINT *fd, XINT *status) +{ + if ((*status = zfd[*fd].nbytes) == ERR) + *status = XERR; + + return (*status); +} + + +/* ZSTTBF -- Return status on a binary file. The same status routine is used + * for both blocked (random access) and streaming (sequential) binary files. + * All character special devices are considered to be streaming files, although + * such is not necessarily the case. Seeks are illegal on character special + * devices. The test for file type is made when the file is opened. + */ +int +ZSTTBF (XINT *fd, XINT *param, XLONG *lvalue) +{ + register struct fiodes *kfp = &zfd[*fd]; + struct stat filstat; + + switch (*param) { + case FSTT_BLKSIZE: + /* If all disk devices do not have the same block size then + * device dependent code should be substituted for the reference + * to SZ_DISKBLOCK below. + */ + if (kfp->flags & KF_NOSEEK) + (*lvalue) = 1L; + else + (*lvalue) = SZ_DISKBLOCK; + break; + + case FSTT_FILSIZE: + /* The file size is undefined if the file is a streaming file. + * For blocked files the file size is determined at open time + * and cached in the kernel file descriptor. The cached value + * is updated when we are called and invalidated whenever the file + * is written to. It is not worthwhile trying to keep track of + * the file size in the kernel because FIO only calls us to + * determine the filesize once, at open time. Caching the size + * saves us one FSTAT system call at open time. + */ + if (kfp->flags & KF_NOSEEK) + (*lvalue) = 0L; + else if ((*lvalue = kfp->filesize) < 0) { + if (fstat ((int)*fd, &filstat) == ERR) + (*lvalue) = XERR; + else + (*lvalue) = kfp->filesize = filstat.st_size; + } + break; + + case FSTT_OPTBUFSIZE: + /* On some systems this parameter may be device dependent in which + * case device dependent code should be substituted here. + */ + (*lvalue) = BF_OPTBUFSIZE; + break; + + case FSTT_MAXBUFSIZE: + /* On some systems this parameter may be device dependent in which + * case device dependent code should be substituted here. + */ + (*lvalue) = BF_MAXBUFSIZE; + break; + + default: + (*lvalue) = XERR; + break; + } + + return (XOK); +} + + +/* _U_FMODE -- Compute the effective file mode, taking into account the + * current process umask. (A no-op at present). + */ +int _u_fmode (int mode) +{ + return (mode); +} + + +/* + * VMcache client interface + * + * vm_access (fname, mode) + * vm_reservespace (nbytes) + * vm_directio (fd, flag) + * + * This small interface implements a subset of the client commands provided + * by the VMcache daemon (virtual memory cache controller). The client + * interface handles connection to the VMcache daemon (if any) transparently + * within the interface. + */ +#include + +#ifdef LINUX +#define USE_SIGACTION +#endif + +#define DEF_ACCESSVAL 1 +#define ENV_VMPORT "VMPORT" +#define ENV_VMCLIENT "VMCLIENT" +#define DEF_VMTHRESH (1024*1024*8) +#define DEF_DIOTHRESH (1024*1024*8) +#define DEF_VMPORT 8677 +#define SZ_CMDBUF 2048 +#define SZ_CNAME 32 + +#ifdef MACOSX +static int vm_enabled = 0; +static int vm_dioenabled = 1; +#else +static int vm_enabled = 1; +static int vm_dioenabled = 0; +#endif + +static int vm_debug = 0; +static int vm_server = 0; +static int vm_initialized = 0; +static int vm_threshold = DEF_VMTHRESH; +static int dio_threshold = DEF_DIOTHRESH; +static int vm_port = DEF_VMPORT; +static char vm_client[SZ_CNAME+1]; + +extern char *getenv(); +extern char *realpath(); +static void vm_initialize(); +static void vm_shutdown(); +static void vm_identify(); +static int vm_write(); +static int vm_connect(); +static int getstr(); + + + +/* VM_ACCESS -- Access a file via the VM subsystem. A return value of 1 + * indicates that the file is (or will be) "cached" in virtual memory, i.e., + * that normal virtual memory file system (normal file i/o) should be used + * to access the file. A return value of 0 indicates that direct i/o should + * be used to access the file, bypassing the virtual memory file system. + */ +int +vm_access (char *fname, int mode) +{ + struct stat st; + char *modestr = NULL, buf[SZ_COMMAND]; + char pathname[SZ_PATHNAME]; + int status; + + + /* One-time process initialization. */ + if (!vm_initialized) + vm_initialize(); + + if (stat (fname, &st) < 0) { + status = DEF_ACCESSVAL; + goto done; + } + + /* If directio is enabled and the file exceeds the directio threshold + * use directio to access the file (access=0). If vmcache is + * disabled use normal VM-based i/o to access the file (access=1). + * If VMcache is enabled we still only use it if the file size + * exceeds vm_threshold. + */ + if (vm_dioenabled) { + status = (st.st_size >= dio_threshold) ? 0 : 1; + goto done; + } else if (!vm_enabled || st.st_size < vm_threshold) { + status = DEF_ACCESSVAL; + goto done; + } + + /* Use of VMcache is enabled and the file equals or exceeds the + * minimum size threshold. Initialization has already been performed. + * Open a VMcache daemon server connection if we don't already have + * one. If the server connection fails we are done, but we will try + * to open a connection again in the next file access. + */ + if (!vm_server) + if (vm_connect() < 0) { + status = DEF_ACCESSVAL; + goto done; + } + + /* Compute the mode string for the server request. */ + switch (mode) { + case READ_ONLY: + modestr = "ro"; + break; + case NEW_FILE: + case READ_WRITE: + case APPEND: + modestr = "rw"; + break; + } + + /* Format and send the file access directive to the VMcache daemon. + * The status from the server is returned as an ascii integer value + * on the same socket. + */ + sprintf (buf, "access %s %s\n", realpath(fname,pathname), modestr); + if (vm_write (vm_server, buf, strlen(buf)) < 0) { + vm_shutdown(); + status = DEF_ACCESSVAL; + goto done; + } + if (read (vm_server, buf, SZ_CMDBUF) <= 0) { + if (vm_debug) + fprintf (stderr, + "vmclient (%s): server not responding\n", vm_client); + vm_shutdown(); + status = DEF_ACCESSVAL; + goto done; + } + + status = atoi (buf); +done: + if (vm_debug) + fprintf (stderr, "vmclient (%s): access `%s' -> %d\n", + vm_client, fname, status); + + return (status < 0 ? DEF_ACCESSVAL : status); +} + + +/* VM_DELETE -- Delete any VM space used by a file, e.g., because the file + * is being physically deleted. This should be called before the file is + * actually deleted so that the cache can determine its device and inode + * values. + */ +int +vm_delete (char *fname, int force) +{ + struct stat st; + char buf[SZ_COMMAND]; + char pathname[SZ_PATHNAME]; + int status = 0; + + /* One-time process initialization. */ + if (!vm_initialized) + vm_initialize(); + + if (stat (fname, &st) < 0) { + status = -1; + goto done; + } + + /* If VMcache is not being used we are done. */ + if (vm_dioenabled && (st.st_size >= dio_threshold)) + goto done; + else if (!vm_enabled || st.st_size < vm_threshold) + goto done; + + /* Don't delete the VM space used by the file if it has hard links + * and only a link is being deleted (force flag will override). + */ + if (st.st_nlink > 1 && !force) + goto done; + + /* Connect to the VMcache server if not already connected. */ + if (!vm_server) + if (vm_connect() < 0) { + status = -1; + goto done; + } + + /* Format and send the delete directive to the VMcache daemon. + * The status from the server is returned as an ascii integer value + * on the same socket. + */ + sprintf (buf, "delete %s\n", realpath(fname,pathname)); + if (vm_write (vm_server, buf, strlen(buf)) < 0) { + vm_shutdown(); + status = -1; + goto done; + } + if (read (vm_server, buf, SZ_CMDBUF) <= 0) { + if (vm_debug) + fprintf (stderr, + "vmclient (%s): server not responding\n", vm_client); + vm_shutdown(); + status = -1; + goto done; + } + + status = atoi (buf); +done: + if (vm_debug) + fprintf (stderr, "vmclient (%s): delete `%s' -> %d\n", + vm_client, fname, status); + + return (status < 0 ? -1 : status); +} + + +/* VM_RESERVESPACE -- Reserve VM space for file data. This directive is + * useful if VM is being used but the VM space could not be preallocated + * at file access time, e.g., when opening a new file. + */ +int +vm_reservespace (long nbytes) +{ + char buf[SZ_CMDBUF]; + int status; + + if (!vm_initialized) + vm_initialize(); + if (!vm_enabled || vm_dioenabled) + return (-1); + if (vm_connect() < 0) + return (-1); + + /* Format and send the file access directive to the VMcache daemon. + * The status from the server is returned as an ascii integer value + * on the same socket. + */ + sprintf (buf, "reservespace %ld\n", nbytes); + if (vm_debug) + fprintf (stderr, "vmclient (%s): %s", vm_client, buf); + + if (vm_write (vm_server, buf, strlen(buf)) < 0) { + vm_shutdown(); + return (-1); + } + if (read (vm_server, buf, SZ_CMDBUF) <= 0) { + if (vm_debug) + fprintf (stderr, + "vmclient (%s): server not responding\n", vm_client); + vm_shutdown(); + return (-1); + } + + status = atoi (buf); + return (status); +} + + +/* VM_IDENTIFY -- Identify the current process to the VM cache server when + * opening a new client connection. + */ +static void +vm_identify (void) +{ + char buf[SZ_CMDBUF]; + + if (vm_write (vm_server, vm_client, strlen(vm_client)) < 0) + vm_shutdown(); + + if (read (vm_server, buf, SZ_CMDBUF) <= 0) { + if (vm_debug) + fprintf (stderr, + "vmclient (%s): server not responding\n", vm_client); + vm_shutdown(); + } +} + + +/* VM_LARGEFILE -- Test if the given offset or file size exceeds the VMcache + * threshold. Zero (false) is returned if the offset is below the threshold + * or if VMcache is disabled. + */ +int +vm_largefile (long nbytes) +{ + return (vm_enabled && nbytes >= vm_threshold); +} + + +/* VM_DIRECTIO -- Turn direct i/o on or off for a file. Direct i/o is raw + * i/o from the device to process memory, bypassing system virtual memory. + */ +int +vm_directio (int fd, int flag) +{ +#ifdef SOLARIS + /* Currently direct i/o is implemented only for Solaris. */ + if (vm_debug > 1) + fprintf (stderr, "vmclient (%s): directio=%d\n", vm_client, flag); + return (directio (fd, flag)); +#else + return (-1); +#endif +} + + +/* VM_INITIALIZE -- Called once per process to open a connection to the + * vmcache daemon. The connection is kept open and is used for all + * subsequent vmcache requests by the process. + */ +static void +vm_initialize (void) +{ + register int ch; + register char *ip, *op; + char token[SZ_FNAME], value[SZ_FNAME]; + extern char os_process_name[]; + char *argp, buf[SZ_FNAME]; + + + /* Extract the process name minus the file path. */ + for (ip=os_process_name, op=vm_client; (*op++ = (ch = *ip)); ip++) { + if (ch == '/') + op = vm_client; + } + + /* Get the server socket port if set in the user environment. */ + if ((argp = getenv (ENV_VMPORT))) + vm_port = atoi (argp); + + /* Get the VM client parameters if an initialization string is + * defined in the user environment. + */ + if ((argp = getenv (ENV_VMCLIENT))) { + while (getstr (&argp, buf, SZ_FNAME, ',') > 0) { + char *modchar, *cp = buf; + int haveval; + + /* Parse "token[=value]" */ + if (getstr (&cp, token, SZ_FNAME, '=') <= 0) + continue; + haveval = (getstr (&cp, value, SZ_FNAME, ',') > 0); + + if (strcmp (token, "enable") == 0) { + vm_enabled = 1; + } else if (strcmp (token, "disable") == 0) { + vm_enabled = 0; + + } else if (strcmp (token, "debug") == 0) { + vm_debug = 1; + if (haveval) + vm_debug = strtol (value, &modchar, 10); + + } else if (strcmp (token, "threshold") == 0 && haveval) { + vm_threshold = strtol (value, &modchar, 10); + if (*modchar == 'k' || *modchar == 'K') + vm_threshold *= 1024; + else if (*modchar == 'm' || *modchar == 'M') + vm_threshold *= (1024 * 1024); + + } else if (strcmp (token, "directio") == 0) { + vm_dioenabled = 1; + if (haveval) { + dio_threshold = strtol (value, &modchar, 10); + if (*modchar == 'k' || *modchar == 'K') + dio_threshold *= 1024; + else if (*modchar == 'm' || *modchar == 'M') + dio_threshold *= (1024 * 1024); + } + } + } + } + + if (vm_debug) { + fprintf (stderr, "vmclient (%s): vm=%d dio=%d ", + vm_client, vm_enabled, vm_dioenabled); + fprintf (stderr, "vmth=%d dioth=%d port=%d\n", + vm_threshold, dio_threshold, vm_port); + } + + /* Attempt to open a connection to the VMcache server. */ + if (vm_enabled && !vm_dioenabled) + vm_connect(); + +#ifdef SUNOS + on_exit (vm_shutdown, NULL); +#else + atexit (vm_shutdown); +#endif + vm_initialized++; +} + + +/* VM_CONNECT -- Connect to the VMcache server. + */ +static int +vm_connect (void) +{ + XINT acmode = READ_WRITE; + char osfn[SZ_FNAME]; + int fd, status = 0; + + extern int ZOPNND(); + + + /* Already connected? */ + if (vm_server) + return (0); + + sprintf (osfn, "inet:%d::", vm_port); + if (vm_debug) + fprintf (stderr, + "vmclient (%s): open server connection `%s' -> ", + vm_client, osfn); + + ZOPNND (osfn, &acmode, &fd); + if (fd == XERR) { + if (vm_debug) + fprintf (stderr, "failed\n"); + status = -1; + } else { + vm_server = fd; + if (vm_debug) + fprintf (stderr, "fd=%d\n", fd); + vm_identify(); + } + + return (status); +} + + +/* VM_SHUTDOWN -- Called at process exit to shutdown the VMcached server + * connection. + */ +static void +vm_shutdown (void) +{ + int status; + extern int ZCLSND(); + + if (vm_server) { + if (vm_debug) + fprintf (stderr, + "vmclient (%s): shutdown server connection\n", vm_client); + vm_write (vm_server, "bye\n", 4); + ZCLSND (&vm_server, &status); + } + vm_server = 0; +} + + +/* VM_WRITE -- Write to the server. We need to encapsulate write so that + * SIGPIPE can be disabled for the duration of the write. We don't want the + * calling process to abort if the VMcache server goes away. + */ +static int +vm_write (int fd, char *buf, int nbytes) +{ + int status; +#ifdef USE_SIGACTION + struct sigaction oldact; +#else + SIGFUNC oldact; +#endif + + if (vm_debug > 1) { + fprintf (stderr, "vmclient (%s):: %s", vm_client, buf); + if (buf[nbytes-1] != '\n') + fprintf (stderr, "\n"); + } + +#ifdef USE_SIGACTION + sigaction (SIGPIPE, NULL, &oldact); + status = write (fd, buf, nbytes); + sigaction (SIGPIPE, &oldact, NULL); +#else + oldact = (SIGFUNC) signal (SIGPIPE, SIG_IGN); + status = write (fd, buf, nbytes); + signal (SIGPIPE, oldact); +#endif + + if (vm_debug && status < 0) + fprintf (stderr, + "vmclient (%s): server not responding\n", vm_client); + + return (status); +} + + +/* GETSTR -- Internal routine to extract a metacharacter delimited substring + * from a formatted string. The metacharacter to be taken as the delimiter + * is passed as an argument. Any embedded whitespace between the tokens is + * stripped. The number of characters in the output token is returned as + * the function value, or zero if EOS or the delimiter is reached. + */ +static int +getstr (char **ipp, char *obuf, int maxch, int delim) +{ + register char *op, *ip = *ipp; + register char *otop = obuf + maxch; + + while (*ip && isspace(*ip)) + ip++; + for (op=obuf; *ip; ip++) { + if (*ip == delim) { + ip++; + break; + } else if (op < otop && !isspace(*ip)) + *op++ = *ip; + } + + *op = '\0'; + *ipp = ip; + + return (op - obuf); +} diff --git a/unix/os/zfioks.c b/unix/os/zfioks.c new file mode 100644 index 00000000..5302dff0 --- /dev/null +++ b/unix/os/zfioks.c @@ -0,0 +1,2101 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SYSV +#include +#else +#include +#endif + +#ifdef MACOSX +#define USE_RCMD 1 +#include +#endif + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_prtype +#define import_spp +#include + + +/* ZFIOKS -- File i/o to a remote kernel server. This driver is the network + * interface for the kernel interface package (sys$ki). The KS driver is + * normally called directly by the KI routines, but is patterned after the + * regular FIO drivers hence may be connected to FIO to provide a network + * interface to the high level code. + * + * zopcks open kernel server on remote node + * zclcks close kernel server + * zardks read from the remote kernel server + * zawrks write to the remote kernel server + * zawtks wait for i/o + * zsttks get channel/device status + * + * In the Berkeley UNIX environment the network interface is TCP/IP. The + * kernel server process irafks.e, running on the remote node, communicates + * directly with the zfioks driver in the iraf client process via a socket. + * Two protocols are supported for connecting to the irafks.e server. The + * default protocol uses RSH (or REMSH) as a bootstrap to start a daemon + * irafks.e process (known as in.irafksd) on the remote node. There is one + * such daemon process per node per user. Login authentication is performed + * when the daemon process is started. Once the daemon is running, each + * instance of the irafks.e server is started by a simple request to the daemon + * without repeating full login authentication, although an authorization code + * is required. The second, or alternate protocol uses REXEC to start the + * irafks.e server directly, and requires that a password be supplied, either + * in the irafhosts file or by an interactive query. + * + * The advantage of the default protocol is efficiency: starting a new server + * is fast once the in.irafksd daemon is up and running on the server node. + * All that is required is a connnect, an irafks.e fork, and another connect. + * There are however some drawbacks (which result from the desire to make all + * this work by boostrapping off of RSH so that no suid root processes are + * needed). First, for things to work correctly it must be possible to assign + * each user a unique port number for the in.irafksd daemon. This is done via + * a uid based scheme. There is however no guarantee that the port will not + * already be in use, preventing the daemon from being bound to the port; if + * this happens, a port is dynamically assigned by the daemon for temporary + * use to set up the server. If this happens a rsh request will be required + * to spawn each server (this involves a total of half a dozen or so forks + * and execs) but it should still work. + * + * The other complication has to do with security. There is a potentially + * serious security problem if the in.irafksd daemon simply responds to + * every request to spawn a kernel server, because any user could issue such + * a request (there is no way to check the uid of the client process over a + * socket connection). In an "open" environment this might be acceptable + * since some effort is required to take advantage of this loophole, but to + * avoid the loophole a simple authentication scheme is used. This involves + * a unique authentication integer which is set by the client when the daemon + * is spawned - spawning the daemon via rsh is "secure" if rsh is secure. + * Subsequent client requests must supply the same authentication number or + * the request will be denied. The authentication number may be specified + * either in the user environment or in the .irafhosts file. The driver will + * automatically set protection 0600 (read-only) on the .irafhosts file when + * accessed. + * + * The driver has a fall-back mode wherein a separate rsh call is used for + * every server connection. This is enabled by setting the in.irafksd port + * number to zero in the irafhosts file. In this mode, all port numbers are + * dynamically assigned, eliminating the chance of collisions with reserved + * or active ports. + */ + +/* + * PORTING NOTE -- A "SysV" system may use rsh instead of remsh. This should + * be checked when doing a port. Also, on at least one system it was necessary + * to change MAXCONN to 1 (maybe that is what it should be anyway). + */ + +extern int errno; +extern int save_prtype; + +#define SZ_NAME 32 /* max size node, etc. name */ +#define SZ_CMD 256 /* max size rexec sh command */ +#define MAXCONN 1 /* for listen */ +#define MAX_UNAUTH 32 /* max unauthorized requests */ +#define ONEHOUR (60*60) /* number of seconds in one hour */ +#define DEF_TIMEOUT (1*ONEHOUR) /* default in.irafksd idle timo */ +#define MIN_TIMEOUT 60 /* minimum timeout (seconds) */ +#define PRO_TIMEOUT 45 /* protocol (ks_geti) timeout */ +#define DEF_HIPORT 47000 /* default in.irafksd port region */ +#define MAX_HIPORT 65535 /* top of port number range */ +#define MIN_HIPORT 15000 /* bottom of port number range */ +#define REXEC_PORT 512 /* port for rexecd daemon */ +#define FNNODE_CHAR '!' /* node name delimiter */ +#define IRAFHOSTS ".irafhosts" /* user host login file */ +#define HOSTLOGIN "dev/irafhosts" /* system host login file */ +#define USER "" /* symbol for user account info */ +#define UNAUTH 99 /* means auth did not match */ + +#ifdef BSD +#define IPPORT_USERRESERVED 5000 +#endif + +#ifdef POSIX +#define SELWIDTH FD_SETSIZE /* number of bits for select */ +#else +#define SELWIDTH 32 /* number of bits for select */ +#endif + +#define KS_RETRY "KS_RETRY" /* number of connection attempts */ +#define KS_NO_RETRY "KS_NO_RETRY" /* env to override rexec retry */ + +#define KSRSH "KSRSH" /* set in env to override RSH cmd */ +#if (defined(BSD) | defined(LINUX)) +#define RSH "rsh" /* typical names are rsh, remsh */ +#else +#ifdef SYSV +#define RSH "remsh" /* typical names are rsh, remsh */ +#else +#define RSH "rsh" /* typical names are rsh, remsh */ +#endif +#endif + +#define IRAFKS_DIRECT 0 /* direct connection (via rexec) */ +#define IRAFKS_CALLBACK 1 /* callback to client socket */ +#define IRAFKS_DAEMON 2 /* in.irafksd daemon process */ +#define IRAFKS_SERVER 3 /* irafks server process */ +#define IRAFKS_CLIENT 4 /* zfioks client process */ + +#define C_RSH 1 /* rsh connect protocol */ +#define C_REXEC 2 /* rexec connect protocol */ +#define C_REXEC_CALLBACK 3 /* rexec-callback protocol */ + +struct ksparam { + int auth; /* user authorization code */ + int port; /* in.irafksd port */ + int hiport; /* in.irafksd port region */ + int timeout; /* in.irafksd idle timeout, sec */ + int protocol; /* connect protocol */ +}; + +int debug_ks = 0; /* print debug info on stderr */ +char debug_file[64] = ""; /* debug output file if nonnull */ +FILE *debug_fp = NULL; /* debugging output */ + +extern uid_t getuid(); +extern char *getenv(); +extern char *strerror(); +static jmp_buf jmpbuf; +static int jmpset = 0; +static int recursion = 0; +static int parent = -1; +static SIGFUNC old_sigcld; +static int ks_pchan[MAXOFILES]; +static int ks_achan[MAXOFILES]; +static int ks_getresvport(), ks_rexecport(); +static int ks_socket(), ks_geti(), ks_puti(), ks_getlogin(); +static void dbgsp(), dbgmsg(), dbgmsgs(); +static void dbgmsg1(), dbgmsg2(), dbgmsg3(), dbgmsg4(); +static char *ks_getpass(); +static void ks_onsig(), ks_reaper(); + +static int ks_getlogin (char *hostname, char *loginname, char *password, + struct ksparam *ks); +static char *ks_username (char *filename, char *pathname, char *username); +static char *ks_sysname (char *filename, char *pathname); +static struct irafhosts *ks_rhosts (char *filename); +static int ks_getword (char **ipp, char *obuf); +static void ks_whosts (struct irafhosts *hp, char *filename); +static char *ks_getpass (char *user, char *host); + +void pr_mask (char *str); + +/* ZOPNKS -- Open a connected subprocess on a remote node. Parse the "server" + * argument to obtain the node name and the command to be issued to connect the + * remote process. Set up a socket to be used for communications with the + * remote irafks kernel server. The "server" string is implementation + * dependent and normally comes from the file dev$hosts. This file is read + * by the high level VOS code before we are called. + */ +int +ZOPNKS ( + PKCHAR *x_server, /* type of connection */ + XINT *mode, /* access mode (not used) */ + XINT *chan /* receives channel code (socket) */ +) +{ + register char *ip, *op; + char *server = (char *)x_server; + char host[SZ_NAME+1], username[SZ_NAME+1], password[SZ_NAME+1]; + int proctype=0, port=0, auth=0, s_port=0, pid=0, s=0, i=0; + struct sockaddr_in from; + char *hostp=NULL, *cmd=NULL; + char obuf[SZ_LINE]; + struct ksparam ks; + + + + /* Initialize local arrays */ + host[0] = username[0] = password[0] = (char) 0; + + /* Parse the server specification. We can be called to set up either + * the irafks daemon or to open a client connection. + * + * (null) direct via rexec + * callback port@host callback client + * in.irafksd [port auth [timeout]] start daemon + * [-prot] [-log file] host!command client connection + * + * where -prot is -rsh, -rex, or -rcb denoting the client connect + * protocols rsh, rexec, and rexec-callback. + */ + + /* Eat any protocol specification strings. The default connect + * protocol is rsh. + */ + for (ip = server; isspace(*ip); ip++) + ; + ks.protocol = C_RSH; + if (strncmp (ip, "-rsh", 4) == 0) { + ks.protocol = C_RSH; + ip += 4; + } else if (strncmp (ip, "-rex", 4) == 0) { + ks.protocol = C_REXEC; + ip += 4; + } else if (strncmp (ip, "-rcb", 4) == 0) { + ks.protocol = C_REXEC_CALLBACK; + ip += 4; + } + + /* Check for the debug log flag. */ + for ( ; isspace(*ip); ip++) + ; + if (strncmp (ip, "-log", 4) == 0) { + debug_ks++; + for (ip += 4; isspace(*ip); ip++) + ; + for (op=debug_file; *ip && !isspace(*ip); ) + *op++ = *ip++; + *op = EOS; + } + + /* Determine connection type. */ + for ( ; isspace(*ip); ip++) + ; + if (!*ip) { + proctype = IRAFKS_DIRECT; + } else if (strncmp (ip, "callback ", 9) == 0) { + proctype = IRAFKS_CALLBACK; + ip += 9; + } else if (strncmp (ip, "in.irafksd", 10) == 0) { + proctype = IRAFKS_DAEMON; + ip += 10; + } else { + proctype = IRAFKS_CLIENT; + cmd = NULL; + for (op=host; *ip != EOS; ip++) + if (*ip == FNNODE_CHAR) { + *op = EOS; + cmd = ++ip; + break; + } else + *op++ = *ip; + if (cmd == NULL) { + *chan = ERR; + goto done; + } + } + + + /* Debug output. If debug_ks is set (e.g. with adb) but no filename + * is given, debug output goes to stderr. + */ + if (debug_ks && !debug_fp) { + if (debug_file[0] != EOS) { + if ((debug_fp = fopen (debug_file, "a")) == NULL) + debug_fp = stderr; + } else + debug_fp = stderr; + } + + + /* Begin debug message log. */ + dbgmsg ("---------------------------------------------------------\n"); + dbgmsg1 ("zopnks (`%s')\n", server); + dbgmsg4 ("kstype=%d, prot=%d, host=`%s', cmd=`%s')\n", + proctype, ks.protocol, host, ip); + parent = getpid(); + + /* + * SERVER side code. + * --------------------- + */ + + if (proctype == IRAFKS_DIRECT) { + /* Kernel server was called by rexec and is already set up to + * communicate on the standard streams. + */ + *chan = 0; + goto done; + + } else if (proctype == IRAFKS_CALLBACK) { + /* The kernel server was called by rexec using the rexec-callback + * protocol. Connect to the client specified socket. + */ + char *client_host; + int port, s; + + /* Parse "port@client_host". */ + for (port=0; isdigit(*ip); ip++) + port = port * 10 + (*ip - '0'); + client_host = ip + 1; + + dbgmsg2 ("S:callback client %s on port %d\n", client_host, port); + if ((s = ks_socket (client_host, NULL, port, "connect")) < 0) + *chan = ERR; + else + *chan = s; + goto done; + + } else if (proctype == IRAFKS_DAEMON) { + /* Handle the special case of spawning the in.irafksd daemon. This + * happens when the zfioks driver is called by the irafks.e process + * which is started up on a remote server node by the networking + * system. (via either rsh or rexec). To start the in.irafksd + * daemon we fork the irafks.e and exit the parent so that the + * rsh|remsh or rexec completes. The daemon will run indefinitely + * or until the specified timeout interval passes without receiving + * any requests. The daemon listens for connections on a global + * (per-user) socket; when a connection is made, the client passes + * in a socket address and authentication code, and provided the + * request is authenticated an irafks server is forked and + * connected to the client socket. The irafks server then runs + * indefinitely, receiving and processing iraf kernel RPCs from the + * client until commanded to shutdown, the connection is broken, or + * an i/o error occurs. + */ + struct timeval timeout; + int check, fromlen, req_port; + int nsec, fd, sig; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + fd_set rfd; +#else + int rfd; +#endif + int once_only = 0; + int detached = 0; + int unauth = 0; + int status = 0; + + /* Get the server parameters. These may be passed either via + * the client in the datastream, or on the command line. The + * latter mode allows the daemon to be run standalone on a server + * node, without the need for a rsh call from a client to start + * the daemon. + */ + while (*ip && isspace (*ip)) + ip++; + if (isdigit (*ip)) { + /* Server parameters were passed on the command line. */ + char *np; + + detached++; + port = req_port = strtol (ip, &np, 10); + if (np == NULL) { + status = 1; + goto d_err; + } else + ip = np; + auth = strtol (ip, &np, 10); + if (np == NULL) { + status = 2; + goto d_err; + } else + ip = np; + nsec = strtol (ip, &np, 10); + if (np == NULL) { + nsec = 0; /* no timeout */ + } else + ip = np; + dbgmsg3 ("S:detached in.irafksd, port=%d, auth=%d, timeout=%d\n", + port, auth, nsec); + + } else { + /* Get client data from the client datastream. */ + if ((req_port = port = ks_geti(0)) < 0) + { status = 1; goto d_err; } + if ((auth = ks_geti(0)) < 0) + { status = 2; goto d_err; } + if ((nsec = ks_geti(0)) < 0) + { status = 3; goto d_err; } + dbgmsg2 ("S:client spawned in.irafksd, port=%d, timeout=%d\n", + port, nsec); + } + + /* Attempt to bind the in.irafksd server socket to the client + * specified port. If this fails a free socket is dynamically + * allocated. If no port is specified (e.g. port=0) the port + * will always be dynamically allocated and a rsh call will be + * employed for every server connection. + */ + if (port <= IPPORT_RESERVED) + port = IPPORT_USERRESERVED - 1; + s = ks_getresvport (&port); + if (s < 0 || listen(s,MAXCONN) < 0) { + status = 4; + goto d_err; + } else if (port != req_port) { + if (detached) { + status = 4; + goto d_err; + } + once_only++; + } + + /* Fork daemon process and return if parent, exiting rsh. */ + dbgmsg2 ("S:fork in.irafksd, port=%d, timeout=%d\n", port, nsec); + pid = fork(); + if (pid < 0) { + status = 4; + goto d_err; + } + + if (pid) { +d_err: dbgmsg1 ("S:in.irafksd parent exit, status=%d\n", status); + if (!detached) { + ks_puti (1, status); + ks_puti (1, port); + } + exit(0); + } + + /* + * What follows is the code for the daemon process. Close the + * stdio streams, which we won't need any more. Create a socket + * and bind it to the given port. Sit in a loop until timeout, + * listening for client connection requests and forking the irafks + * server in response to each such request. + */ + + dbgmsg3 ("S:in.irafksd started, pid=%d ppid=%d\n", + getpid(), getppid()); + old_sigcld = (SIGFUNC) signal (SIGCHLD, (SIGFUNC)ks_reaper); + + /* Reset standard streams to console to record error messages. */ + fd = open ("/dev/null", 0); close(0); dup(fd); close(fd); + fd = open ("/dev/console", 1); close(1); dup(fd); close(fd); + fd = open ("/dev/console", 2); close(2); dup(fd); close(fd); + + /* Loop forever or until the idle timeout expires, waiting for a + * client connection. + */ + for (;;) { + timeout.tv_sec = nsec; + timeout.tv_usec = 0; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + FD_ZERO(&rfd); + FD_SET(s,&rfd); +#else + rfd = (1 << s); +#endif + status = 0; + + /* Wait until either we get a connection, or a previously + * started server exits. + */ + jmpset++; + if ((sig = setjmp(jmpbuf))) { + if (sig == SIGCHLD) { + dbgmsg ("S:in.irafksd sigchld return\n"); + while (waitpid ((pid_t)0, (int *)0, WNOHANG) > 0) + ; + } else + exit (0); + } + if (select (SELWIDTH,&rfd,NULL,NULL, nsec ? &timeout : 0) <= 0) + exit (0); + + /* Accept the connection. */ + if ((fd = accept (s, (struct sockaddr *)0, + (socklen_t *)0)) < 0) { + fprintf (stderr, + "S:in.irafksd: accept on port %d failed\n", port); + exit (2); + } else + dbgmsg ("S:in.irafksd: connection established\n"); + + /* Find out where the connection is coming from. */ + fromlen = sizeof (from); +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + if (getpeername (fd, (struct sockaddr *)&from, + (socklen_t *)&fromlen) < 0) { +#else + if (getpeername (fd, &from, (socklen_t *)&fromlen) < 0) { +#endif + fprintf (stderr, "in.irafksd: getpeername failed\n"); + exit (3); + } + + /* Connection established. Get client data. */ + if ((s_port = ks_geti(fd)) < 0 || (check = ks_geti(fd)) < 0) { + fprintf (stderr, "in.irafksd: protocol error\n"); + status = 1; + goto s_err; + } + + /* Verify authorization. Shutdown if repeated unauthorized + * requests occur. + */ + if (auth && check != auth) { + if (unauth++ > MAX_UNAUTH) { + fprintf (stderr, + "in.irafksd: unauthorized connection attempt\n"); + exit (4); + } + status = UNAUTH; + goto s_err; + } + + /* Connection authorized if this message is output. */ + dbgmsg1 ("S:in.irafksd: client port = %d\n", s_port); + + /* Fork the iraf kernel server. */ + pid = fork(); + if (pid < 0) { + fprintf (stderr, "in.irafksd: process creation failed\n"); + status = 3; + goto s_err; + } + + if (pid) { /** parent **/ +s_err: dbgmsg1 ("S:in.irafksd fork complete, status=%d\n", + status); + ks_puti (fd, status); + close (fd); + if (once_only) + exit (0); + /* otherwise loop indefinitely */ + + } else { /** child **/ + /* Set up iraf kernel server. */ + u_long n_addr, addr; + unsigned char *ap = (unsigned char *)&n_addr; + + dbgmsg2 ("S:irafks server started, pid=%d ppid=%d\n", + getpid(), getppid()); + signal (SIGCHLD, old_sigcld); + /* + old_sigcld = (SIGFUNC) signal (SIGCHLD, (SIGFUNC)ks_reaper); + */ + close (fd); close (s); + + n_addr = from.sin_addr.s_addr; + addr = ntohl(n_addr); + sprintf (obuf, "%d.%d.%d.%d", ap[0],ap[1],ap[2],ap[3]); + dbgmsg2 ("S:client address=%s port=%d\n", obuf, s_port); + + if ((s = ks_socket (NULL, addr, s_port, "connect")) < 0) { + dbgmsg1 ("S:irafks connect to port %d failed\n", s_port); + fprintf (stderr, "irafks: cannot connect to client\n"); + exit (1); + } else + dbgmsg1 ("S:irafks connected on port %d\n", s_port); + + *chan = s; + goto done; + } + } + } /* else fall through to DAEMON_CLIENT code */ + + /* + * CLIENT side code. + * --------------------- + */ + + /* Attempt to fire up the kernel server process. Get login name + * and password for the named host. If a password is given attempt + * to connect via the rexec protocol, otherwise attempt the connection + * via the rsh/in.irafksd protocol. + */ + if (ks_getlogin (host, username, password, &ks) == ERR) { + *chan = ERR; + + } else if (ks.protocol == C_REXEC) { + /* Use rexec protocol. We start the remote kernel server with + * rexec and communicate via the socket returned by rexec. + */ + hostp = host; + dbgmsg2 ("C:rexec for host=%s, user=%s\n", host, username); +#ifdef USE_RCMD + *chan = rcmd (&hostp, ks_rexecport(), + getlogin(), username, cmd, 0); +#else + *chan = rexec (&hostp, ks_rexecport(), username, password, cmd, 0); +#endif + + } else if (ks.protocol == C_REXEC_CALLBACK) { + /* Use rexec-callback protocol. In this case the remote kernel + * server is started with rexec, but we have the remote server + * call us back on a private socket. This guarantees a direct + * socket connection for use in cases where the standard i/o + * streams set up for rexec do not provide a direct connection. + */ + char localhost[SZ_FNAME]; + char callback_cmd[SZ_LINE]; + struct hostent *hp; + int tfd=0, fd=0, ss=0; + + /* Get reserved port for direct communications link. */ + s_port = IPPORT_USERRESERVED - 1; + s = ks_getresvport (&s_port); + if (s < 0) + goto r_err; + + /* Ready to receive callback from server. */ + if (listen (s, MAXCONN) < 0) + goto r_err; + + /* Compose rexec-callback command: "cmd port@client-host". */ + if (gethostname (localhost, SZ_FNAME) < 0) + goto r_err; + if ((hp = gethostbyname (localhost)) == NULL) + goto r_err; + sprintf (callback_cmd, "%s callback %d@%s", + cmd, s_port, hp->h_name); + dbgmsg2 ("rexec to host %s: %s\n", host, callback_cmd); + + hostp = host; + dbgmsg3 ("rexec for host=%s, user=%s, using client port %d\n", + host, username, s_port); +#ifdef USE_RCMD + ss = rcmd (&hostp, ks_rexecport(), + getlogin(), username, callback_cmd, 0); +#else + ss = rexec (&hostp, + ks_rexecport(), username, password, callback_cmd, 0); +#endif + + /* Wait for the server to call us back. */ + dbgmsg1 ("waiting for connection on port %d\n", s_port); + if ((tfd = accept (s, (struct sockaddr *)0, (socklen_t *)0)) < 0) { +r_err: dbgmsg ("rexec-callback connect failed\n"); + close(s); close(ss); + *chan = ERR; + } else { + close(s); fd = dup(tfd); close(tfd); + dbgmsg1 ("connected to irafks server on fd=%d\n", fd); + *chan = fd; + + /* Mark the rexec channel for deletion at close time when + * the i/o socket is closed. + */ + for (i=0; i < MAXOFILES; i++) + if (!ks_pchan[i]) { + ks_pchan[i] = fd; + ks_achan[i] = ss; + break; + } + } + + } else { + /* Use the default protocol, which avoids passwords. This uses + * rsh to start up (once) the iraf networking daemon in.irafksd + * on the remote node, and thereafter merely places requests to + * in.irafksd to spawn each instance of the irafks.e server. + */ + char command[SZ_LINE], *nretryp; + int pin[2], pout[2]; + int status = 0; + int ntries = 0, nretries = 0; + char *password; + int fd, tfd; + int t=0, s=0; + + /* Get reserved port for client. */ + s_port = IPPORT_USERRESERVED - 1; + s = ks_getresvport (&s_port); + if (s < 0) { + status |= 01; + goto c_err; + } + dbgmsg2 ("C:connect to in.irafksd host=%s client port=%d\n", + host, s_port); + + /* Ready to receive callback from server. */ + if (listen (s, MAXCONN) < 0) { + status |= 02; + goto c_err; + } + + /* Check for the number of connection attempts. */ + if ((nretryp = getenv(KS_RETRY))) + nretries = atoi(nretryp); + + /* in.irafkd port. */ + port = ks.port; +again: + /* Connect to in.irafksd daemon on server system and send request + * to start up a new irafks daemon on the port just created. If + * the connection fails, fork an rsh and start up the in.irafksd. + */ + if (!port || (t = ks_socket (host, NULL, port, "connect")) < 0) { + dbgmsg ("C:no server, fork rsh to start in.irafksd\n"); + + if (pipe(pin) < 0 || pipe(pout) < 0) { + status |= 04; + goto c_err; + } + pid = fork(); + if (pid < 0) { + status |= 010; + goto c_err; + } + + if (pid) { + /* Pass target port and authorization code to in.irafksd. + * Server returns the actual port assigned. + */ + close (pin[1]); + close (pout[0]); +retry: + dbgmsg2 ("C:send port=%d, timeout=%d to irafks.e\n", + ks.port, ks.timeout); + if (ks_puti (pout[1], port) <= 0) + status |= 0020; + if (ks_puti (pout[1], ks.auth) <= 0) + status |= 0040; + if (ks_puti (pout[1], ks.timeout) <= 0) + status |= 0100; + if (ks_geti(pin[0])) + status |= 0200; + + port = ks_geti (pin[0]); + dbgmsg1 ("C:irafks.e returns port=%d\n", port); + + /* Wait for the rsh connection to shut down. */ + while (read (pin[0], obuf, SZ_LINE) > 0) + ; + wait (NULL); + close (pin[0]); + if (pout[1] != pin[0]) + close (pout[1]); + + /* If the rsh succeeded the in.irafksd daemon should be + * running now. Attempt again to connect. If this fails, + * most likely the rsh failed. Try to use rexecd to start + * the daemon. + */ + if (status || + (t = ks_socket (host, NULL, port, "connect")) < 0) { + + /* The KS_RETRY environment variable may be set to + * the number of times we wish to try to reconnect. + * We'll sleep for 1-second between attempts before + * giving up. + */ + if (getenv (KS_RETRY) && nretries--) { + sleep (1); + goto again; + } + + /* If KS_NO_RETRY is set then we won't try at all + * with an rexec. These two variables give us a + * chance to retry using the rsh/KSRSH protocol some + * number of times before failing, and optionally + * trying with a different (rexec) before quitting + * entirely. On most recent systems the rexec port + * isn't enabled anyway. + */ + if (getenv (KS_NO_RETRY) || ntries++) { + status |= 0400; + goto c_err; + } + + dbgmsg ("C:rsh failed - try rexec\n"); + if (!(password = ks_getpass (username, host))) + { status |= 01000; goto c_err; } + + sprintf (command, "%s in.irafksd", cmd); + dbgmsg3 ("C:rexec %s@%s: %s\n", username, host, command); + + hostp = host; +#ifdef USE_RCMD + fd = rcmd (&hostp, ks_rexecport(), + getlogin(), username, command, 0); +#else + fd = rexec (&hostp, ks_rexecport(), + username, password, command, NULL); +#endif + + if (fd < 0) { + status |= 02000; + goto c_err; + } else { + status = 0; + port = ks.port; + pin[0] = pout[1] = fd; + goto retry; + } + } + + } else { + /* Call rsh to start up in.irafksd on server node. + */ + char *s, *rshcmd; + + close (pin[0]); close (pout[1]); + close (0); dup (pout[0]); close (pout[0]); + close (1); dup (pin[1]); close (pin[1]); + + rshcmd = (s = getenv(KSRSH)) ? s : RSH; + + dbgmsg3 ("C:exec rsh %s -l %s `%s' in.irafksd\n", + host, username, cmd); + execlp (rshcmd, rshcmd, + host, "-l", username, cmd, "in.irafksd", NULL); + exit (1); + } + } + + /* Send command to start up irafks server. This consists of the + * reserved port for the server connection followed by the + * authorization code. The in.irafksd daemon returns a status + * byte which will be zero if the operation is successful. + */ + dbgmsg1 ("C:request irafks server for client port %d\n", s_port); + + if (ks_puti (t, s_port) <= 0) + { status |= 004000; goto c_err; } + if (ks_puti (t, ks.auth) <= 0) + { status |= 010000; goto c_err; } + + /* Check for an authorization failure and connect on a dynamically + * allocated port if this happens (in.irafksd will allocate the + * port). An authorization failure does not necessarily indicate + * an unauthorized connection attempt; it may mean instead that + * the user has not set up the same authorization code on two + * different nodes and iraf clients on both nodes, with different + * authorization codes, are trying to access the same server. + * If this happens the first client will get the in.irafksd daemon + * and the other client will have to do an rsh connect each time. + */ + if ((status = ks_geti(t))) { + if (port && status == UNAUTH) { + close(t); + port = 0; + dbgmsg ("C:authorization failed, retry with port=0\n"); + status = 0; + goto again; + } else { + status |= 020000; + goto c_err; + } + } + + /* Wait for the server to call us back. */ + if ((tfd = accept (s, (struct sockaddr *)0, (socklen_t *)0)) < 0) { +c_err: dbgmsg1 ("C:zfioks client status=%o\n", status); + close(t); close(s); + kill (pid, SIGTERM); + *chan = ERR; + } else { + close(t); close(s); fd = dup(tfd); close(tfd); + dbgmsg1 ("C:connected to irafks server on fd=%d\n", fd); + *chan = fd; + } + } + +done: + jmpset = 0; + if (*chan > 0) { + if (*chan < MAXOFILES) + zfd[*chan].nbytes = 0; + else { + close (*chan); + *chan = ERR; + } + } + + dbgmsg1 ("zopnks returns status=%d\n", *chan); + + return (*chan); +} + + +/* ZCLSKS -- Close a kernel server connection. + */ +int +ZCLSKS ( + XINT *chan, /* socket to kernel server */ + XINT *status /* receives close status */ +) +{ + int i; + + /* Close the primary channel. */ + *status = close (*chan); + + /* Close any alternate channels associated with the primary. */ + for (i=0; i < MAXOFILES; i++) { + if (ks_pchan[i] == *chan) { + close (ks_achan[i]); + ks_pchan[i] = 0; + } + } + + dbgmsg2 ("server [%d] terminated, status = %d\n", *chan, *status); + + return (*status); +} + + +/* ZARDKS -- Read from the kernel server channel. No attempt is made to + * impose a record structure upon the channel, as is the case with IPC. + * In UNIX the channel is stream oriented and it is up to the caller to + * unblock records from the input stream. Data blocks are assumed to be + * preceded by headers telling how much data to read, hence we read from + * the channel until the specified number of bytes have been read or ERR + * or EOF is seen on the stream. + */ +int +ZARDKS ( + XINT *chan, /* kernel server channel (socket) */ + XCHAR *buf, /* output buffer */ + XINT *totbytes, /* total number of bytes to read */ + XLONG *loffset /* not used */ +) +{ +#ifdef ANSI + volatile char *op; + volatile int fd, nbytes; +#else + char *op; + int fd, nbytes; +#endif + SIGFUNC sigint, sigterm; + int status = ERR; + + fd = *chan; + op = (char *)buf; + zfd[fd].nbytes = nbytes = *totbytes; + if (debug_ks > 1) + dbgmsg2 ("initiate read of %d bytes from KS channel %d\n", + nbytes, fd); + + /* Now read exactly nbytes of data from channel into user buffer. + * Return actual byte count if EOF is seen. If ERR is seen return + * ERR. If necessary multiple read requests are issued to read the + * entire record. Reads are interruptable but the interrupt is caught + * and returned as a read error on the server channel. + */ + sigint = (SIGFUNC) signal (SIGINT, (SIGFUNC)ks_onsig); + sigterm = (SIGFUNC) signal (SIGTERM, (SIGFUNC)ks_onsig); + + while (nbytes > 0) { + jmpset++; + if (setjmp (jmpbuf) == 0) + status = read (fd, op, nbytes); + else + status = ERR; + + switch (status) { + case 0: + zfd[fd].nbytes -= nbytes; + return (XERR); + case ERR: + zfd[fd].nbytes = ERR; + return (XERR); + default: + nbytes -= status; + op += status; + break; + } + } + + jmpset = 0; + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); + if (debug_ks > 1) + dbgmsg2 ("read %d bytes from KS channel %d:\n", op-(char *)buf, fd); + + return (status); +} + + +/* ZAWRKS -- Write to a kernel server channel. + */ +int +ZAWRKS ( + XINT *chan, /* kernel server channel (socket) */ + XCHAR *buf, /* output buffer */ + XINT *totbytes, /* number of bytes to write */ + XLONG *loffset /* not used */ +) +{ + SIGFUNC sigint, sigterm, sigpipe; +#ifdef ANSI + volatile int fd, nbytes; + volatile int ofd; +#else + int fd, nbytes; + int ofd; +#endif + + /* If chan=0 (the process standard input) then we really want to + * write to channel 1, the standard output. + */ + if ((ofd = fd = *chan) == 0) + ofd = 1; + + zfd[fd].nbytes = nbytes = *totbytes; + if (debug_ks > 1) + dbgmsg2 ("initiate write of %d bytes to KS channel %d\n", + nbytes, ofd); + + /* Write exactly nbytes of data to the channel from user buffer to + * the channel. Block interrupt during the write to avoid corrupting + * the data stream protocol if the user interrupts the client task. + * Trap SIGPIPE and return it as a write error on the channel instead. + * Likewise, turn an interrupt into a write error on the channel. + */ + sigint = (SIGFUNC) signal (SIGINT, (SIGFUNC)ks_onsig); + sigterm = (SIGFUNC) signal (SIGTERM, (SIGFUNC)ks_onsig); + sigpipe = (SIGFUNC) signal (SIGPIPE, (SIGFUNC)ks_onsig); + recursion = 0; + + jmpset++; + if (setjmp (jmpbuf) == 0) + zfd[fd].nbytes = write (ofd, (char *)buf, nbytes); + else + zfd[fd].nbytes = ERR; + + jmpset = 0; + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); + signal (SIGPIPE, sigpipe); + if (debug_ks > 1) + dbgmsg2 ("wrote %d bytes to KS channel %d:\n", zfd[fd].nbytes, ofd); + + return (XOK); +} + + +/* KS_ONSIG -- Catch a signal. + */ +static void +ks_onsig ( + int sig, /* signal which was trapped */ + int *arg1, /* not used */ + int *arg2 /* not used */ +) +{ + /* If we get a SIGPIPE writing to a server the server has probably + * died. Make it look like there was an i/o error on the channel. + */ + if (sig == SIGPIPE && recursion++ == 0) + fputs ("kernel server process has died\n", stderr); + + if (jmpset) + longjmp (jmpbuf, sig); +} + + +/* KS_REAPER -- Catch a SIGCHLD signal and reap all children. + */ +static void +ks_reaper ( + int sig, /* signal which was trapped */ + int *arg1, /* not used */ + int *arg2 /* not used */ +) +{ + int status=0, pid=0; + + while ((pid = waitpid ((pid_t)0, (int *) &status, WNOHANG)) > 0) + dbgmsg2 ("ks_reaper -- pid=%d, status=%d\n", pid, status); + + if (jmpset) + longjmp (jmpbuf, sig); +} + + +/* ZAWTKS -- Wait for i/o to a KS channel. Since UNIX i/o is not asynchronous + * we do not really wait, rather we return the status value (byte count) from + * the last read or write to the channel. + */ +int +ZAWTKS (XINT *chan, XINT *status) +{ + if ((*status = zfd[*chan].nbytes) == ERR) + *status = XERR; + + return (*status); +} + + +/* ZSTTKS -- Get binary file status for an KS channel. A KS channel is a + * streaming binary file. + */ +int +ZSTTKS ( + XINT *chan, /* not used; all KS channels have same status */ + XINT *param, + XLONG *lvalue +) +{ + switch (*param) { + case FSTT_BLKSIZE: + case FSTT_FILSIZE: + *lvalue = 0; + break; + case FSTT_OPTBUFSIZE: + *lvalue = KS_OPTBUFSIZE; + break; + case FSTT_MAXBUFSIZE: + *lvalue = KS_MAXBUFSIZE; + break; + default: + *lvalue = XERR; + } + + return (XOK); +} + + +/* + * Internal routines. + * ------------------- + */ + +/* KS_SOCKET -- Get a socket configured for the given host and port. Either + * bind the socket to the port and make it ready for connections, or connect + * to the remote socket at the given address. + */ +static int +ks_socket (host, addr, port, mode) +char *host; +u_long addr; +int port; +char *mode; +{ + struct sockaddr_in sockaddr; + struct hostent *hp; + int s; + + /* Create socket. */ + if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) + return (ERR); + + /* Set socket address. */ + bzero ((char *)&sockaddr, sizeof(sockaddr)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons((short)port); + + /* Get address of server host. */ + if (addr) { + sockaddr.sin_addr.s_addr = htonl((long)addr); + } else if (*host) { + if ((hp = gethostbyname (host)) == NULL) + goto failed; + bcopy((char *)hp->h_addr,(char *)&sockaddr.sin_addr, hp->h_length); + } else + sockaddr.sin_addr.s_addr = INADDR_ANY; + + /* Either bind and listen for connnections, or connect to a remote + * socket. + */ + if (strncmp (mode, "listen", 1) == 0) { + if (bind (s, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) + goto failed; + if (listen (s, MAXCONN) < 0) + goto failed; + } else if (strncmp (mode, "connect", 1) == 0) { + if (connect(s,(struct sockaddr *)&sockaddr,sizeof(sockaddr)) < 0) + goto failed; + } else + goto failed; + + return (s); + +failed: + dbgmsg2 ("ks_socket: errno=%d (%s)\n", errno, strerror(errno)); + close (s); + return (ERR); +} + + +/* KS_GETRESVPORT -- Open a socket and attempt to bind it to the given port. + * Locate a usable port if this fails. The actual port is returned in the + * output argument. + */ +static int +ks_getresvport (alport) +int *alport; +{ + struct sockaddr_in sin; + int s; + + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + s = socket (AF_INET, SOCK_STREAM, 0); + if (s < 0) + return (-1); + + for (;;) { + sin.sin_port = htons((u_short)*alport); +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) >= 0) { +#else + if (bind(s, (caddr_t)&sin, sizeof (sin)) >= 0) { +#endif + return (s); + } + if (errno != EADDRINUSE) { + (void) close(s); + return (-1); + } + dbgmsg4 ("ks_getresvport: decr errno=%d (%s) alport=%d -> %d\n", + errno, strerror(errno), *alport, *alport - 1); + (*alport)--; + if (*alport == IPPORT_RESERVED) { + (void) close(s); + errno = EAGAIN; /* close */ + return (-1); + } + } +} + + +/* KS_REXECPORT -- Return the port for the rexec system service. + */ +static int +ks_rexecport() +{ + register struct servent *sv; + static int port = 0; + + if (port) + return (port); + + if ((sv = getservbyname ("exec", "tcp"))) + return (port = sv->s_port); + else + return (port = REXEC_PORT); +} + + +/* KS_PUTI -- Write an integer value to the output stream as a null terminated + * ascii string. + */ +static int +ks_puti (fd, ival) +int fd; +int ival; +{ + char obuf[SZ_FNAME]; + + sprintf (obuf, "%d", ival); + return (write (fd, obuf, strlen(obuf)+1)); +} + + +/* KS_GETI -- Read a positive integer value, passed as a null terminated ascii + * string, base decimal, from the given stream. + */ +static int +ks_geti (fd) +int fd; +{ + register int value = 0; + struct timeval timeout; + int stat, sig; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + fd_set rfd; +#else + int rfd; +#endif + char ch; + + jmpset++; + if ((sig = setjmp(jmpbuf))) + if (sig == SIGCHLD) + waitpid ((pid_t)0, (int *)0, WNOHANG); + + timeout.tv_sec = PRO_TIMEOUT; + timeout.tv_usec = 0; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + FD_ZERO(&rfd); + FD_SET(fd,&rfd); +#else + rfd = (1 << fd); +#endif + + /* Read and accumulate a decimal integer. Timeout if the client + * does not respond within a reasonable period. + */ + do { + if (select (SELWIDTH, &rfd, NULL, NULL, &timeout) <= 0) { + dbgmsg ("ks_geti: timeout on read\n"); + jmpset = 0; + return (ERR); + } + + if ((stat = read (fd, &ch, 1)) <= 0) { + dbgmsg3 ("ks_geti: read status=%d, errno=%d (%s)\n", + stat, errno, strerror(errno)); + jmpset = 0; + return (ERR); + } + + if (ch) { + if (isdigit(ch)) + value = value * 10 + (ch - '0'); + else { + dbgmsg1 ("ks_geti: read char=%o\n", ch); + jmpset = 0; + return (ERR); + } + } + } while (ch); + + jmpset = 0; + return (value); +} + + +/* KS_GETS -- Read a null terminated ascii string. +static int +ks_gets (fd, outstr) +int fd; +char *outstr; +{ + register char *op = outstr; + int stat; + + do { + if ((stat = read (fd, op, 1)) <= 0) { + dbgmsg3 ("ks_gets: read status=%d, errno=%d (%s)\n", + stat, errno, strerror(errno)); + return (ERR); + } + } while (*op++); + + return (op - outstr - 1); +} + */ + + +/* KS_MSG -- Print debugging messages. + */ +static void dbgsp (pid) +int pid; +{ + int i, nsp = ((parent > 0) ? (pid - parent) : 0); + for (i=0; i < nsp; i++) + fprintf (debug_fp, " "); +} + +static void +dbgmsg (msg) +char *msg; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, "%s", msg); + } +} +static void +dbgmsgs (fmt, arg) +char *fmt; +char *arg; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg); + fflush (debug_fp); + } +} +static void +dbgmsg1 (fmt, arg) +char *fmt; +int arg; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg); + fflush (debug_fp); + } +} +static void +dbgmsg2 (fmt, arg1, arg2) +char *fmt; +int arg1, arg2; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg1, arg2); + fflush (debug_fp); + } +} +static void +dbgmsg3 (fmt, arg1, arg2, arg3) +char *fmt; +int arg1, arg2, arg3; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg1, arg2, arg3); + fflush (debug_fp); + } +} +static void +dbgmsg4 (fmt, arg1, arg2, arg3, arg4) +char *fmt; +int arg1, arg2, arg3, arg4; +{ + int pid; + if (debug_ks) { + fprintf (debug_fp, "[%5d] ", (pid = getpid())); dbgsp(pid); + fprintf (debug_fp, fmt, arg1, arg2, arg3, arg4); + fflush (debug_fp); + } +} + + +/* + * Stuff for processing the irafhosts file. + * ---------------------------------------- + */ + +#define MAX_HEADERLINES 128 +#define MAX_NODES 256 +#define SZ_SBUF 4096 +#define DEFAULT (-1) +#define KSAUTH "KSAUTH" + +struct irafhosts { + int port; + int auth; + int hiport; + int timeout; + int nheaderlines; + int nparams; + int mode; + char *header[MAX_HEADERLINES]; + int nnodes; + struct nodelist { + char *name; + char *login; + char *password; + int port; + int auth; + int hiport; + int timeout; + int protocol; + } node[MAX_NODES]; + int sbuflen; + char sbuf[SZ_SBUF]; +}; + + +/* KS_GETLOGIN -- Read the irafhosts file to determine how to connect to + * the indicated host. If the user has a .irafhosts file read that, otherwise + * read the system default irafhosts file in iraf$dev. Fill in or correct + * any networking parameters as necessary. If the rsh protocol is enabled + * and the user does not have a .irafhosts file, create one for them. If + * the user has a .irafhosts file but a unique authorization code has not + * yet been assigned, assign one and write a new file. Ensure that the + * file has read-only access privileges. + */ +static int +ks_getlogin ( + char *hostname, /* node we wish a login for */ + char *loginname, /* receives the login name */ + char *password, /* receives the login password */ + struct ksparam *ks /* networking parameters */ +) +{ + register struct irafhosts *hp; + register int i; + char userfile[SZ_PATHNAME]; + char sysfile[SZ_PATHNAME]; + char fname[SZ_PATHNAME]; + char username[SZ_NAME]; + char *namep, *authp; + struct nodelist *np; + int update = 0; + int auth; + + /* Get path to user irafhosts file. */ + if (ks_username (IRAFHOSTS, userfile, username) == NULL) + return (ERR); + + /* Read user irafhosts file if there is one. Check for an old-style + * irafhosts file, and read the system file instead if the user file + * is the old obsolete version. + */ + if ((hp = ks_rhosts (userfile))) { + /* Old style irafhosts file? */ + if (hp->nparams == 0) { + /* Attempt to preserve old file with .OLD extension. */ + strcpy (fname, username); + strcat (fname, ".OLD"); + unlink (fname); + rename (username, fname); + + /* Read system file instead. */ + free ((char *)hp); + if (ks_sysname (HOSTLOGIN, sysfile) == NULL) + return (ERR); + if ((hp = ks_rhosts (sysfile)) == NULL) + return (ERR); + update++; + } + } else { + /* Use system default irafhosts. */ + if (ks_sysname (HOSTLOGIN, sysfile) == NULL) + return (ERR); + if ((hp = ks_rhosts (sysfile)) == NULL) + return (ERR); + update++; + } + + /* Search the node list for an entry for the named host. + */ + for (i=0, np=NULL; i < hp->nnodes; i++) { + namep = hp->node[i].name; + if (strcmp (hostname, namep) == 0 || strcmp (namep, "*") == 0) { + np = &hp->node[i]; + break; + } + } + + /* Get the login name. If this is "disable" networking is disabled + * for the given node entry. + */ + if (np->login[0] && strcmp(np->login,"disable") == 0) { + free ((char *)hp); + return (ERR); + } else if (np->login[0] && strcmp(np->login,USER) != 0) { + strcpy (loginname, np->login); + } else + strcpy (loginname, username); + + /* Get the password. */ + if (np->password[0]) { + if (strcmp (np->password, USER) == 0) { + if (ks->protocol == C_RSH) + password[0] = EOS; + else + goto query; /* need a valid password for rexec */ + } else if (strcmp (np->password, "?") == 0) { +query: if ((namep = ks_getpass (loginname, hostname))) + strcpy (password, namep); + else + password[0] = EOS; + } else + strcpy (password, np->password); + } else + password[0] = EOS; + + /* + * Set up ksparam structure. Check to see if any of the irafhosts + * parameter values are out of range; if so, set the default values, + * and mark the file for updating. + * + * NOTE -- If possible, the user should have the same port number and + * authorization code (e.g., same .irafhosts file) on all local nodes. + * All we can do here is manage the file on the local node. It is up + * to the user to make all the files the same. + */ + + /* The port number for the in.irafksd daemon should be unique for + * every user, as well as unique in the sense that no other network + * service uses the port. This is impossible to guarantee, but we + * can come close by choosing port numbers in the high range for a + * short integer, using the user's UID to attempt to give each user + * a unique port. + */ + if (hp->hiport == DEFAULT) { + ks->hiport = DEF_HIPORT; + } else if (hp->hiport > MAX_HIPORT || hp->hiport < MIN_HIPORT) { + ks->hiport = DEF_HIPORT; + hp->hiport = DEFAULT; + update++; + } else + ks->hiport = hp->hiport; + + if (hp->port == 0) + ks->port = 0; + else if (hp->port > MAX_HIPORT || hp->port < IPPORT_RESERVED) + ks->port = ks->hiport - (getuid() % 10000); + else + ks->port = hp->port; + + /* Every user should also have a unique authorization code. It should + * be next to impossible to predict apriori, so that user A cannot + * predict user B's authorization code. The number is arbitary, + * and can be changed by the user by editing the irafhosts file. + * Any heuristic which produces a reasonable unique and unpredictable + * number will do. We use the system clock time and a snapshot of + * the machine registers as saved in a setjmp. Given that any iraf + * program can cause a network request which results in generation + * of an authorization code, and the point at which this request + * occurs during the execution of a task is arbtrary, the register + * set should be pretty unpredictable. + */ + if (hp->auth == DEFAULT) { + jmp_buf jmpbuf; + int value; + + setjmp (jmpbuf); + value = time(NULL); + for (i=0; i < sizeof(jmpbuf)/sizeof(int); i++) + value ^= ((int *)jmpbuf)[i]; + value = (value << 13) / 1000 * 1000; + if (value < 0) + value = -value; + value += (getuid() % 1000); + ks->auth = hp->auth = value; + update++; + } else + ks->auth = hp->auth; + + /* If KSAUTH is defined in the user environment this overrides the + * value given in the .irafhosts file. This allows authorization + * codes to be dynamically allocated at login time if someone doesn't + * want to take the risk of having their authorization code in the + * irafhosts file. + */ + if ((authp = getenv(KSAUTH)) && (auth = atoi(authp))) + ks->auth = auth; + + /* The timeout value is the time in seconds after which the in.irafksd + * daemon will shutdown if idle. + */ + if (hp->timeout == DEFAULT) { + ks->timeout = DEF_TIMEOUT; + } else if (hp->timeout < MIN_TIMEOUT) { + ks->timeout = DEF_TIMEOUT; + hp->timeout = DEFAULT; + update++; + } else + ks->timeout = hp->timeout; + + /* Check for any node specific KS parameter overrides. */ + if (np->port) + ks->port = np->port; + if (np->auth) + ks->auth = np->auth; + if (np->hiport) + ks->hiport = np->hiport; + if (np->timeout) + ks->timeout = np->timeout; + if (np->protocol) + ks->protocol = np->protocol; + + dbgmsg1 ("ks.port = %d\n", ks->port); + dbgmsg1 ("ks.hiport = %d\n", ks->hiport); + dbgmsg1 ("ks.timeout = %d\n", ks->timeout); + + /* Update irafhosts if necessary. */ + if (update || (hp->mode & 077)) + ks_whosts (hp, userfile); + + free ((char *)hp); + return (0); +} + + +/* KS_USERNAME -- Convert the given filename into a user home directory + * relative pathname. A pointer to a buffer containing the pathname is + * returned as the function value. If the pointer "username" is nonnull + * the user's name is returned as well. + */ +static char * +ks_username (char *filename, char *pathname, char *username) +{ + register struct passwd *pwd; + + pwd = getpwuid (getuid()); + if (pwd == NULL) + return (NULL); + + strcpy (pathname, pwd->pw_dir); + strcat (pathname, "/"); + strcat (pathname, IRAFHOSTS); + if (username) + strcpy (username, pwd->pw_name); + + endpwent(); + return (pathname); +} + + +/* KS_SYSNAME -- Convert the given filename into an iraf$dev pathname. + * A pointer to a buffer containing the pathname is returned as the + * function value. + */ +static char * +ks_sysname (char *filename, char *pathname) +{ + XCHAR irafdir[SZ_PATHNAME+1]; + XINT x_maxch=SZ_PATHNAME, x_nchars; + extern int ZGTENV(); + + + ZGTENV ("iraf", irafdir, &x_maxch, &x_nchars); + if (x_nchars <= 0) + return (NULL); + + strcpy (pathname, (char *)irafdir); + strcat (pathname, HOSTLOGIN); + + return (pathname); +} + + +/* KS_RHOSTS -- Read the named irafhosts file into a descriptor, returning + * the descriptor as the function value. + */ +static struct irafhosts * +ks_rhosts (char *filename) +{ + char lbuf[SZ_LINE]; + char word[SZ_LINE]; + struct irafhosts *hp; + struct nodelist *np; + struct stat st; + char *ip, *op; + int value; + FILE *fp; + + dbgmsgs ("read %s\n", filename); + + /* Open irafhosts file. */ + if ((fp = fopen (filename, "r")) == NULL) + return (NULL); + + /* Get descriptor. */ + hp = (struct irafhosts *) malloc (sizeof(struct irafhosts)); + if (hp == NULL) { + fclose (fp); + return (NULL); + } + + hp->port = DEFAULT; + hp->auth = DEFAULT; + hp->hiport = DEF_HIPORT; + hp->timeout = DEF_TIMEOUT; + hp->nheaderlines = 0; + hp->nparams = 0; + hp->nnodes = 0; + hp->mode = 0; + op = hp->sbuf; + + if (fstat (fileno(fp), &st) == 0) + hp->mode = st.st_mode; + + /* Get file header. */ + while (fgets (op, SZ_LINE, fp)) + if (op[0] == '#' || isspace(op[0])) { + hp->header[hp->nheaderlines++] = op; + op += strlen(op) + 1; + } else { + strcpy (lbuf, op); + break; + } + + /* Everything else is a parameter assignment (param =), a node + * entry (node :), or ignored. + */ + do { + ip = lbuf; + if (*ip == '#' || isspace(*ip)) + continue; + + ks_getword (&ip, word); + while (*ip && isspace(*ip)) + ip++; + + if (*ip == '=') { + for (ip++; *ip && isspace(*ip); ip++) + ; + if (strncmp (ip, "default", 7) == 0) + value = DEFAULT; + else if (isdigit (*ip)) + value = atoi (ip); + else + value = 0; + + if (strcmp (word, "port") == 0) + hp->port = value; + else if (strcmp (word, "auth") == 0) + hp->auth = value; + else if (strcmp (word, "hiport") == 0) + hp->hiport = value; + else if (strcmp (word, "timeout") == 0) + hp->timeout = value; + /* else disregard */ + + hp->nparams++; + + } else if (*ip == ':') { + /* Node entry. + */ + np = &hp->node[hp->nnodes++]; /* nodename */ + strcpy (op, word); + np->name = op; + op += strlen(op) + 1; + + ip++; + ks_getword (&ip, word); /* loginname */ + strcpy (op, word); + np->login = op; + op += strlen(op) + 1; + + ks_getword (&ip, word); /* password */ + strcpy (op, word); + np->password = op; + op += strlen(op) + 1; + + /* Process any optional networking paramaeter overrides. + * These are in the form param=value where param is port, + * auth, etc. + */ + np->port = 0; + np->auth = 0; + np->hiport = 0; + np->timeout = 0; + np->protocol = 0; + + while (ks_getword (&ip, word)) { + if (strncmp (word, "port=", 5) == 0) { + np->port = atoi (word + 5); + } else if (strncmp (word, "auth=", 5) == 0) { + np->auth = atoi (word + 5); + } else if (strncmp (word, "hiport=", 7) == 0) { + np->hiport = atoi (word + 7); + } else if (strncmp (word, "timeout=", 8) == 0) { + np->timeout = atoi (word + 8); + } else if (strncmp (word, "protocol=", 9) == 0) { + if (strcmp (word + 9, "rsh") == 0) + np->protocol = C_RSH; + else if (strcmp (word + 9, "rex") == 0) + np->protocol = C_REXEC; + else if (strcmp (word + 9, "rcb") == 0) + np->protocol = C_REXEC_CALLBACK; + } + } + } + + hp->sbuflen = op - hp->sbuf; + if (hp->sbuflen + SZ_LINE > SZ_SBUF) + break; + + } while (fgets (lbuf, SZ_LINE, fp)); + + fclose (fp); + return (hp); +} + + +/* KS_GETWORD -- Get a quoted or whitespace delimited word. + */ +static int +ks_getword (char **ipp, char *obuf) +{ + register char *ip = *ipp, *op = obuf; + + while (*ip && isspace(*ip)) + ip++; + + if (*ip == '"') { + for (ip++; *ip && *ip != '"'; ) + *op++ = *ip++; + } else { + while (*ip && !isspace(*ip)) + *op++ = *ip++; + } + + *op = EOS; + *ipp = ip; + return (op - obuf); +} + + +/* KS_WHOSTS -- Write out a hosts file from the internal descriptor to disk. + */ +static void +ks_whosts ( + struct irafhosts *hp, + char *filename +) +{ + register char *ip; + struct nodelist *np; + int fd, q, i; + FILE *fp; + + dbgmsgs ("update %s\n", filename); + + /* Open new irafhosts file. */ + unlink (filename); + if ((fd = creat (filename, 0600)) < 0) + return; + if ((fp = fdopen(fd,"w")) == NULL) { + close (fd); + unlink (filename); + return; + } + + /* Output any header comments. */ + for (i=0; i < hp->nheaderlines; i++) + fputs (hp->header[i], fp); + + /* Output the networking parameters. */ + if (hp->port == DEFAULT) + fprintf (fp, "port = default\n"); + else + fprintf (fp, "port = %d\n", hp->port); + fprintf (fp, "auth = %d\n", hp->auth); + if (hp->hiport == DEFAULT) + fprintf (fp, "hiport = default\n"); + else + fprintf (fp, "hiport = %d\n", hp->hiport); + if (hp->timeout == DEFAULT) + fprintf (fp, "timeout = default\n"); + else + fprintf (fp, "timeout = %d\n", hp->timeout); + fprintf (fp, "\n"); + + /* Output each "node : login password" sequence, quoting the login + * and password strings if they contain any whitespace. + */ + for (i=0; i < hp->nnodes; i++) { + np = &hp->node[i]; + + /* Output username. */ + fprintf (fp, "%s\t:", np->name); + for (q=0, ip=np->login; *ip && !(q = isspace(*ip)); ip++) + ; + fprintf (fp, q ? " \"%s\"" : " %s", np->login); + + /* Output password field. */ + for (q=0, ip=np->password; *ip && !(q = isspace(*ip)); ip++) + ; + fprintf (fp, q ? " \"%s\"" : " %s", np->password); + + /* Add any optional parameter overrides given in the file when + * originally read. + */ + if (np->port) + fprintf (fp, " port=%d", np->port); + if (np->auth) + fprintf (fp, " auth=%d", np->auth); + if (np->hiport) + fprintf (fp, " hiport=%d", np->hiport); + if (np->timeout) + fprintf (fp, " timeout=%d", np->timeout); + if (np->protocol) { + fprintf (fp, " protocol="); + switch (np->protocol) { + case C_REXEC: + fprintf (fp, "rex"); + break; + case C_REXEC_CALLBACK: + fprintf (fp, "rcb"); + break; + default: + fprintf (fp, "rsh"); + break; + } + } + + fprintf (fp, "\n"); + } + + fclose (fp); +} + + +/* KS_GETPASS -- Access the terminal in raw mode to get the user's + * password. + */ +static char *ks_getpass (char *user, char *host) +{ + static char password[SZ_NAME]; + char prompt[80]; + int tty, n; +#ifdef SYSV + struct termios tc, tc_save; +#else + struct sgttyb ttystat; + int sg_flags; +#endif + + if ((tty = open ("/dev/tty", 2)) == ERR) + return (NULL); + + sprintf (prompt, "Password (%s@%s): ", user, host); + write (tty, prompt, strlen(prompt)); + +#ifdef SYSV + tcgetattr (tty, &tc); + tc_save = tc; + + tc.c_lflag &= + ~(0 | ECHO | ECHOE | ECHOK | ECHONL); + tc.c_oflag |= + (0 | TAB3 | OPOST | ONLCR); + tc.c_oflag &= + ~(0 | OCRNL | ONOCR | ONLRET); + + tc.c_cc[VMIN] = 1; + tc.c_cc[VTIME] = 0; + tc.c_cc[VLNEXT] = 0; + + tcsetattr (tty, TCSADRAIN, &tc); +#else + ioctl (tty, TIOCGETP, &ttystat); + sg_flags = ttystat.sg_flags; + ttystat.sg_flags &= ~ECHO; + ioctl (tty, TIOCSETP, &ttystat); +#endif + + n = read (tty, password, SZ_NAME); + write (tty, "\n", 1); + +#ifdef SYSV + tcsetattr (tty, TCSADRAIN, &tc_save); +#else + ttystat.sg_flags = sg_flags; + ioctl (tty, TIOCSETP, &ttystat); +#endif + + close (tty); + + if (n <= 0) + return (NULL); + else + password[n-1] = EOS; + + return (password); +} + + +/* PR_MASK -- Debug routine to print the current SIGCHLD mask. + */ +void pr_mask (char *str) +{ + sigset_t sigset, pending; + + if (sigprocmask (0, NULL, &sigset) < 0) + dbgmsg ("sigprocmask error"); + + dbgmsg (str); + if (sigismember (&sigset, SIGCHLD)) + dbgmsg ("pr_mask: SIGCHLD set\n"); + + if (sigpending (&pending) < 0) + dbgmsg ("sigpending error"); + if (sigismember (&pending, SIGCHLD)) + dbgmsg ("\tpr_mask: SIGCHLD pending\n"); +} diff --git a/unix/os/zfiolp.c b/unix/os/zfiolp.c new file mode 100644 index 00000000..e07571a2 --- /dev/null +++ b/unix/os/zfiolp.c @@ -0,0 +1,239 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_prtype +#define import_spp +#include + +/* + * ZFIOLP -- IRAF FIO interface to the line printer device. The line printer + * is opened as a streaming type (no seeks) write-only binary device. + * On systems like UNIX in which the line printer is just another file, + * the interface is trivial; we just call the FIOBF routines. On other + * systems it might be necessary to spool the output to a binary file + * and dispose of the file to a queue when the printer file is closed, + * or some such thing. + * + * Currently, the CL-callable LPRINT program is the only thing in IRAF which + * writes to the printer. Most programs are intended to write to their + * standard output or a file, which is subsequently copied to the printer + * by the user or by a CL level script. LPRINT uses "dev$*.tty" descriptors + * and the TTY interface to learn the characteristics of the printer (eject + * is considered equivalent to a tty clear, for example). + * + * The system and device dependent information necessary to perform these + * functions is contained in three strings passed as the "printer" parameter + * to ZOPNLP. The strings come from the TERMCAP entry for the device. + * The format of such a string is + * + * device D spoolfile D dispose_cmd EOS + * + * where DEVICE is the logical device name (not used herein), D is the field + * delimiter character (the first nonalphnumeric character encountered after + * the device field), SPOOLFILE is a UNIX pathname to be passed to MKTEMP + * to create the spoolfile pathname, and DISPOSE_CMD is a fill-in-the-blanks + * template for a UNIX shell command which will dispose of the spoolfile to + * the printer device. + */ + +extern int save_prtype; + +#define SZ_OSCMD 512 /* buffer for dispose cmd */ +#define SZ_LPSTR 256 /* zopnlp plotter argument */ + +struct lprinter { + char *name; /* logical gdevice name */ + char *spoolfile; /* spoolfile string */ + char *dispose; /* dispose format string */ +}; + +struct oprinter { + struct lprinter *lp; /* device code as above */ + long wbytes; /* nbytes written to device */ + char spoolfile[SZ_PATHNAME+1]; +}; + +struct lprinter dpr; /* device table */ +struct oprinter lpr; /* printer descriptor */ +int lpr_inuse = NO; /* set if printer is open */ +char lpstr[SZ_LPSTR+1]; /* save zopnlp argument */ + + +extern int ZOPNBF (), ZCLSBF (), ZOSCMD (), ZFDELE (), ZARDBF (); +extern int ZAWRBF (), ZAWTBF (), ZSTTBF (); + + +/* ZOPNLP -- Open a printer device for binary file i/o. If we can talk + * directly to the printer, do so, otherwise open a spoolfile which is + * to be sent to the printer when ZCLSLP is later called. + */ +int +ZOPNLP ( + PKCHAR *printer, /* logical name of printer device */ + XINT *mode, /* file access mode */ + XINT *chan /* UNIX file number (output) */ +) +{ + register char *ip; + static char delim; + int fd; + + + /* We do not see a need to have more than one printer open at + * a time, and it makes things simpler. We can easily generalize + * to multiple open printer devices in the future if justified. + */ + if (lpr_inuse == YES) { + *chan = XERR; + return (XERR); + } else + lpr_inuse = YES; + + /* Parse the printer string into the name, spoolfile, and dispose + * strings. + */ + strncpy (lpstr, (char *)printer, SZ_LPSTR); + lpstr[SZ_LPSTR] = EOS; + + /* Locate NAME field. */ + dpr.name = lpstr; + for (ip=lpstr; isalnum(*ip); ip++) + ; + delim = *ip; + *ip++ = EOS; + + /* Locate SPOOLFILE field. */ + for (dpr.spoolfile=ip; *ip && *ip != delim; ip++) + ; + *ip++ = EOS; + + /* Locate DISPOSE field. */ + for (dpr.dispose=ip; *ip && *ip != delim; ip++) + ; + *ip++ = EOS; + + /* Initialize the open printer descriptor. + */ + lpr.wbytes = 0L; + lpr.lp = &dpr; + strcpy (lpr.spoolfile, dpr.spoolfile); + if (dpr.dispose[0] != EOS) + if ((fd = mkstemp (lpr.spoolfile)) >= 0) { + fchmod (fd, 0644); + close (fd); + } + + return ZOPNBF ((PKCHAR *)lpr.spoolfile, mode, chan); +} + + +/* ZCLSLP -- To close a printer we merely close the "spoolfile", and then + * dispose of the spoolfile to the OS if so indicated. + */ +int +ZCLSLP (XINT *chan, XINT *status) +{ + static PKCHAR xnullstr[1] = { XEOS }; + register char *ip, *op, *f; + PKCHAR cmd[(SZ_LINE+1) / sizeof(PKCHAR)]; + XINT junk; + + ZCLSBF (chan, status); + lpr_inuse = NO; + + /* Dispose of the output file if so indicated. Do not bother to + * check the status return, since we cannot return status to FIO + * from here anyhow. Do not dispose of the file if it is empty. + * If the file is disposed of by the OS, we assume that it is also + * deleted after printing. If file is not disposed to the OS, we + * delete it ourselves. + */ + if (*(lpr.lp->dispose) != EOS) { + if (lpr.wbytes > 0) { + PKCHAR out[SZ_FNAME+1]; + + /* Build up command line by substituting the spoolfile name + * everywhere the macro "$F" appears in the "dispose" text. + */ + op = (char *)cmd; + for (ip=lpr.lp->dispose; (*op = *ip++) != EOS; op++) + if (*op == '$' && *ip == 'F') { + for (f=lpr.spoolfile; (*op = *f++) != EOS; op++) + ; + /* Overwrite EOS, skip over 'F' */ + --op, ip++; + } + strcpy ((char *)out, + save_prtype == PR_CONNECTED ? "/dev/tty" : ""); + ZOSCMD (cmd, xnullstr, out, out, &junk); + } else + ZFDELE ((PKCHAR *)lpr.spoolfile, &junk); + } + + return (*status); +} + + +/* ZARDLP -- Initiate a read from the line printer device. For UNIX, the read + * and write routines are just the binary file i/o routines. Note that packing + * of chars into bytes, mapping of escape sequences, etc. is done by the high + * level code; our function is merely to move the data to the device. The read + * primitive is not likely to be needed for a printer, but you never know... + */ +int +ZARDLP (XINT *chan, XCHAR *buf, XINT *maxbytes, XLONG *offset) +{ + XLONG dummy_offset = 0; + + return ZARDBF (chan, buf, maxbytes, &dummy_offset); +} + + +/* ZAWRLP -- Initiate a write to the line printer. Keep track of the number + * of bytes written so we know whether or not to dispose of the spoolfile + * at close time. + */ +int +ZAWRLP (XINT *chan, XCHAR *buf, XINT *nbytes, XLONG *offset) +{ + XLONG dummy_offset = 0; + + lpr.wbytes += *nbytes; + return ZAWRBF (chan, buf, nbytes, &dummy_offset); +} + + +/* ZAWTLP -- Wait for i/o and return the status of the channel, i.e., the + * number of bytes read or written or XERR. + */ +int +ZAWTLP (XINT *chan, XINT *status) +{ + return ZAWTBF (chan, status); +} + + +/* ZSTTLP -- Get status for the line printer output file. We call ZSTTBF since + * the output file was opened by ZOPNBF. The actual output file may be either + * a blocked or streaming file depending on whether the output is spooled. + */ +int +ZSTTLP (XINT *chan, XINT *param, XLONG *lvalue) +{ + switch (*param) { + case FSTT_BLKSIZE: + *lvalue = 0L; /* streaming device */ + break; + default: + return ZSTTBF (chan, param, lvalue); + } + return (*lvalue); +} diff --git a/unix/os/zfiomt.c b/unix/os/zfiomt.c new file mode 100644 index 00000000..e3c3a890 --- /dev/null +++ b/unix/os/zfiomt.c @@ -0,0 +1,1911 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _AIX +#include +#define MTIOCTOP STIOCTOP +#define MTBSF STRSF +#define MTBSR STRSR +#define MTFSF STFSF +#define MTFSR STFSR +#define MTREW STREW +#define MTWEOF STWEOF +#define mtop stop +#define mt_op st_op +#define mt_count st_count +#else +#ifndef MACOSX +#include +#endif +#endif + +/* Define if status logging to sockets is desired. */ +#define TCPIP + +#ifdef TCPIP +#include +#include +#include +#include +#define DEFPORT 5138 +#endif + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_stdarg +#define import_spp +#include + +/* + * ZFIOMT.C -- Programmable magtape kernel interface for UNIX/IRAF systems. + * This file contains only the lowest level i/o routines. Most of the + * functionality of the iraf magtape i/o system is provided by the routines + * in the VOS interfaces MTIO and ETC. The dev$tapecap file is used to + * describe the magtape devices present on the local host system, and to + * characterize the behavior of each device. + * + * The behavior of this driver is controlled by parameters given in the + * entry for a magtape device in the tapecap file. The following parameters + * are defined (not all of which are necessarily used by the driver). + * + * CODE TYPE DEFAULT DESCRIPTION + * + * bs i 0 device block size (0 if variable) + * dn s 0 density + * dt s generic drive type + * fb i 1 default FITS blocking factor (recsize=fb*2880) + * fe i 0 time to FSF equivalent in file Kb + * fs i 0 approximate filemark size (bytes) + * mr i 65535 maximum record size + * or i 63360 optimum record size + * rs i 0 approximate record gap size (bytes) + * ts i 0 tape capacity (Mb) + * tt s unknown tape type + * + * al s none device allocation info + * dv s required i/o (no-rewind) device file + * lk s required lock file root name (uparm$mt.lok) + * rd s none rewind device file + * so s none status output device file or socket + * + * bo b no BSF positions to BOF + * ce b no ignore close status on CLRO + * eo b no do not write double EOT on CLWO (VMS) + * fc b no device does a FSF on CLRO + * ir b no treat all read errors as EOF + * mf b no enable multifile FSF for forward positioning + * nb b no device cannot backspace + * nf b/i no/0 rewind and space forward to backspace file + * np b no disable all positioning ioctls + * ow b no backspace and overwrite EOT at append + * re b no read at EOT returns ERR + * rf b no use BSR,FSR to space over filemarks + * ro b no rewind on every open to define position + * rr b no rewind at close-readonly to define position + * se b no device will position past EOT in a read + * sk b no skip record forward after a read error + * ue b no force update of EOT (search for EOT) + * wc b no OPWR-CLWR at EOF writes null file + * + * ct i builtin MTIOCTOP code + * bf i builtin BSF ioctl code + * br i builtin BSR ioctl code + * ff i builtin FSF ioctl code + * fr i builtin FSR ioctl code + * ri i builtin REW ioctl code + * + * + * Many of these parameters are optional. Some are used by the high level MTIO + * code rather than the host level driver. + * + * The externally callable driver routines are the following. + * + * ZZOPMT (device, acmode, devcap, devpos, newfile, chan) + * ZZRDMT (chan, buf, maxbytes, offset) + * ZZWRMT (chan, buf, nbytes, offset) + * ZZWTMT (chan, devpos, status) + * ZZSTMT (chan, param, value) + * ZZCLMT (chan, devpos, status) + * ZZRWMT (device, devcap, status) + * + * Here, "device" is the name by which the device is known to the driver, + * acmode is the access mode, devcap is the tapecap device entry, devpos is a + * structure giving the current tape position, amount of tape used, etc, and + * newfile is the requested file. The driver will position to the indicated + * file at open time. The devpos structure is passed in to the driver at open + * time and returned to the client at close time. While i/o is in progress + * the driver is responsible for keeping track of the device position. The + * client is responsible for maintaining the position information while the + * device is closed. If the position is uncertain devpos should be passed in + * with a negative file number and the driver will rewind to reestablish a + * known position. + * + * The devpos structure (struct _mtpos) has the following fields: + * + * int filno file number + * int recno record number + * int nfiles number of files on tape + * int tapeused total amount of storage used (Kb) + * int pflags bitflags describing last i/o operation + * + * FILNO and RECNO are negative if the position is undefined (unknown). File + * number 1 is the first file. NFILES is the total number of files on the + * tape. NFILES less than or equal to zero indicates that the number of files + * is unknown; the driver will set nfiles when EOT is seen. The tape can be + * positioned to EOT by opening the device at file NFILES+1. TAPEUSED refers + * to the total amount of tape used at EOT, regardless of the current tape + * position. The driver will keep track of tape usage using the device + * attributes specified in the tapecap entry. TAPEUSED less than or equal to + * zero indicates that the amount of tape used is unknown. Both the tape + * capacity and TAPEUSED are given in Kb (1024 byte blocks). + * + * + * The following bitflags are defined: + * + * MF_ERR i/o error occurred in last operation + * MF_EOF a tape mark was seen in the last operation + * MF_EOT end of tape seen in the last operation + * MF_EOR a record advance occurred in the last operation + * + * The PFLAGS field is output only, and is cleared at the beginning of each + * i/o request. + */ + +extern int errno; +typedef unsigned int U_int; + +#define CONSOLE "/dev/console" +#define MAX_ERRIGNORE 10 /* max errs before skiprec */ +#define MAX_ERRCNT 20 /* max errs before EOF */ +#define MAXDEV 8 /* max open magtape devices */ +#define MAXREC 64512 /* default maximum record size */ +#define OPTREC 64512 /* default optimum record size */ +#define SHORTREC 20 /* short record for dummy files */ +#define RDONLY 0 /* read only */ +#define WRONLY 1 /* write only */ + +/* Tape position information (must agree with client struct). This is input + * by the client at open time and is only modified locally by the driver. + */ +struct _mtpos { + int filno; /* current file (1=first) */ + int recno; /* current record (1=first) */ + int nfiles; /* number of files on tape */ + int tapeused; /* total tape used (Kb) */ + int pflags; /* i/o status bitflags (output) */ +}; + +/* MTPOS bitflags. */ +#define MF_ERR 0001 /* i/o error occurred in last operation */ +#define MF_EOF 0002 /* a tape mark was seen in the last operation */ +#define MF_EOT 0004 /* end of tape seen in the last operation */ +#define MF_EOR 0010 /* a record advance occurred in the last operation */ + +/* General magtape device information, for status output. */ +struct mtdev { + FILE *statusout; /* status out or NULL */ + int blksize; /* device block size */ + int recsize; /* last record size */ + int maxrec; /* maximum record size */ + int optrec; /* optimum record size */ + int tapesize; /* tape capacity (Kb) */ + int eofsize; /* filemark size, bytes */ + int gapsize; /* interrecord gap size, bytes */ + int maxbsf; /* BSF vs rewind-FSF threshold */ + char density[SZ_FNAME]; /* tape density, bpi */ + char devtype[SZ_FNAME]; /* drive type */ + char tapetype[SZ_FNAME]; /* tape type */ + char statusdev[SZ_FNAME]; /* status output device */ +}; + +/* Magtape device descriptor. */ +#define get_mtdesc(fd) ((struct mtdesc *)zfd[fd].fp) +#define set_mtdesc(fd,mp) zfd[fd].fp = (FILE *)mp +#define ateot(pp) (pp->nfiles>0 && pp->filno==pp->nfiles+1 && pp->recno==1) +#define spaceused(pp) ((pp->nfiles==0 || pp->filno>pp->nfiles) && !ateot(pp)) + +struct mtdesc { + XINT *chan; /* file descriptor open device */ + int flags; /* device characteristics */ + int acmode; /* access mode */ + int errcnt; /* i/o error count */ + int nbytes; /* status of last i/o transfer */ + int tbytes; /* byte portion of tapeused */ + int mtrew; /* REW ioctl code */ + int mtbsr, mtfsr; /* BSR,FSR ioctl codes */ + int mtbsf, mtfsf; /* BSF,FSF ioctl codes */ + U_int mtioctop; /* MTIOCTOP code */ + struct _mtpos mtpos; /* position information */ + struct mtdev mtdev; /* drive type information */ + char iodev[SZ_FNAME]; /* i/o device */ + char nr_device[SZ_FNAME]; /* no-rewind-on-close device */ + char rw_device[SZ_FNAME]; /* rewind-on-close device */ +}; + +/* Parameter codes. */ +#define P_AL 1 /* allocation stuff */ +#define P_BF 2 /* MTBSF */ +#define P_BO 3 /* BSF positions to BOF */ +#define P_BR 4 /* MTBSR */ +#define P_BS 5 /* block size */ +#define P_CE 6 /* ignore close status on CLRO */ +#define P_CT 7 /* MTIOCTOP */ +#define P_DN 8 /* density */ +#define P_DT 9 /* drive type id string */ +#define P_DV 10 /* no rewind device */ +#define P_EO 11 /* do not write double EOT on CLWO (VMS) */ +#define P_FC 12 /* device does FSF on close readonly */ +#define P_FF 13 /* MTFSF */ +#define P_FR 14 /* MTFSR */ +#define P_FS 15 /* filemark size, Kb */ +#define P_IR 16 /* map read errors to EOF */ +#define P_MF 17 /* enable multifile FSF for fwd positioning */ +#define P_MR 18 /* max record (i/o transfer) size */ +#define P_NB 19 /* backspace not allowed */ +#define P_NF 20 /* rewind and space forward to posn back */ +#define P_NP 21 /* disable file positioning */ +#define P_OR 22 /* optimum record size */ +#define P_OW 23 /* optimize EOT (9tk drives) */ +#define P_RD 24 /* rewind device */ +#define P_RE 25 /* read at EOT returns ERR */ +#define P_RF 26 /* use record skip ioctls to skip filemarks */ +#define P_RI 27 /* MTREW */ +#define P_RO 28 /* rewind on every open to define position */ +#define P_RR 29 /* rewind after close-readonly */ +#define P_RS 30 /* interrecord gap size, bytes */ +#define P_SE 31 /* BSF needed after read at EOT */ +#define P_SK 32 /* skip record forward after read error */ +#define P_SO 33 /* status output device or socket */ +#define P_TS 34 /* tape size, Mb */ +#define P_TT 35 /* tape type id string */ +#define P_UE 36 /* force update of EOT (search for EOT) */ +#define P_WC 37 /* open-write/close creates dummy EOT */ + +/* Tapecap device characteristic bitflags. */ +#define BO 00000001 /* BSF positions to BOF */ +#define CE 00000002 /* ignore close status on CLRO */ +#define EO 00000004 /* do not write double EOT on CLWO (VMS) */ +#define FC 00000010 /* defines does a FSF on CLRO */ +#define IR 00000020 /* treat all read errors as EOF */ +#define MF 00000040 /* enable multifile FSF for fwd positioning */ +#define NB 00000100 /* device cannot backspace */ +#define NF 00000200 /* rewind and space forward to position back */ +#define NP 00000400 /* disable file positioning */ +#define OW 00001000 /* backspace and overwrite at append */ +#define RD 00002000 /* rewind-on-close device specified */ +#define RE 00004000 /* read at EOT can signal error */ +#define RF 00010000 /* use BSR,FSR to space over filemarks */ +#define RO 00020000 /* rewind on every open to define position */ +#define RR 00040000 /* rewind after close-readonly */ +#define SE 00100000 /* read at EOT leaves past tape mark */ +#define SK 00200000 /* skip record forward after a read error */ +#define UE 00400000 /* force update of EOT (search for EOT) */ +#define WC 01000000 /* CLWR at EOF writes null file */ + +/* Device characteristic codes. */ +#define PNAME(a,b) ((((int)(a))<<8)+(int)(b)) + +/* Tape drives aren't supported on Mac systems currently. +*/ +#ifndef MACOSX + +/* Device flag table. */ +static struct mtchar { + int pname; /* 2 byte parameter name code */ + int pcode; /* parameter number */ + int bitflag; /* flag bit */ + int valset; /* value has been set */ +} devpar[] = { + { PNAME('a','l'), P_AL, 0, 0 }, + { PNAME('b','f'), P_BF, 0, 0 }, + { PNAME('b','o'), P_BO, BO, 0 }, + { PNAME('b','r'), P_BR, 0, 0 }, + { PNAME('b','s'), P_BS, 0, 0 }, + { PNAME('c','e'), P_CE, CE, 0 }, + { PNAME('c','t'), P_CT, 0, 0 }, + { PNAME('d','n'), P_DN, 0, 0 }, + { PNAME('d','t'), P_DT, 0, 0 }, + { PNAME('d','v'), P_DV, 0, 0 }, + { PNAME('e','o'), P_EO, EO, 0 }, + { PNAME('f','c'), P_FC, FC, 0 }, + { PNAME('f','f'), P_FF, 0, 0 }, + { PNAME('f','r'), P_FR, 0, 0 }, + { PNAME('f','s'), P_FS, 0, 0 }, + { PNAME('i','r'), P_IR, IR, 0 }, + { PNAME('m','f'), P_MF, MF, 0 }, + { PNAME('m','r'), P_MR, 0, 0 }, + { PNAME('n','b'), P_NB, NB, 0 }, + { PNAME('n','f'), P_NF, NF, 0 }, + { PNAME('n','p'), P_NP, NP, 0 }, + { PNAME('o','r'), P_OR, 0, 0 }, + { PNAME('o','w'), P_OW, OW, 0 }, + { PNAME('r','d'), P_RD, 0, 0 }, + { PNAME('r','e'), P_RE, RE, 0 }, + { PNAME('r','f'), P_RF, RF, 0 }, + { PNAME('r','i'), P_RI, 0, 0 }, + { PNAME('r','o'), P_RO, RO, 0 }, + { PNAME('r','r'), P_RR, RR, 0 }, + { PNAME('r','s'), P_RS, 0, 0 }, + { PNAME('s','e'), P_SE, SE, 0 }, + { PNAME('s','k'), P_SK, SK, 0 }, + { PNAME('s','o'), P_SO, 0, 0 }, + { PNAME('t','s'), P_TS, 0, 0 }, + { PNAME('t','t'), P_TT, 0, 0 }, + { PNAME('u','e'), P_UE, UE, 0 }, + { PNAME('w','c'), P_WC, WC, 0 }, + { 0, 0, 0, 0 }, +}; + + +static int zmtgetfd(); +static int zmtbsr(), zmtbsf(), zmtfsr(), zmtfsf(); +static int zmtclose(), zmtfpos(), zmtrew(); + +static int zmtopen (char *dev, int u_acmode); +static int zmtclose (int fd); +static struct mtdesc *zmtdesc (char *device, int acmode, char *devcap, + struct _mtpos *devpos); +static int zmtfpos (struct mtdesc *mp, int newfile); +static int zmtrew (int fd); +static void zmtfls (struct mtdesc *mp); +static void zmtfree (struct mtdesc *mp); +static int zmtfsf (int fd, int nfiles); +static int zmtbsf (int fd, int nfiles); +static int zmtfsr (int fd, int nrecords); +static int zmtbsr (int fd, int nrecords); + +static void zmtdbgn (struct mtdesc *mp, const char *argsformat, ... ); +static void zmtdbg (struct mtdesc *mp, char *msg); +static void zmtdbgopen (struct mtdesc *mp); +static void zmtdbgclose (struct mtdesc *mp); + + + + +/* ZZOPMT -- Open the named magtape device and position to the given file. + * On output, "newfile" contains the number of the file actually opened, + * which may be less than what was requested if EOT is reached. + */ +int +ZZOPMT ( + PKCHAR *device, /* device name */ + XINT *acmode, /* access mode: read_only or write_only for tapes */ + PKCHAR *devcap, /* tapecap entry for device */ + XINT *devpos, /* pointer to tape position info struct */ + XINT *newfile, /* file to be opened or EOT */ + XINT *chan /* OS channel of opened file */ +) +{ + register int fd; + register struct mtdesc *mp; + struct _mtpos *pp; + + /* Open the main device descriptor. */ + mp = zmtdesc ((char *)device, *acmode, (char *)devcap, + (struct _mtpos *)devpos); + if (mp == NULL) { + *chan = XERR; + return (XERR); + } + + zmtdbgn (mp, "open device %s\n", (char *)device); + + /* Save the channel pointer for the delayed open used for file + * positioning. If file positioning is needed the device will + * be opened read-only, so that an interrupt occurring while seeking + * to EOT for writing will not result in truncation of the tape! + * BE SURE TO RETURN OSCHAN as soon as the device is physically + * opened, so that the error recovery code can close the file if + * we are interrupted. + */ + mp->chan = chan; + *chan = 0; + + /* Initialize the descriptor. */ + mp->errcnt = 0; + mp->tbytes = 0; + pp = &mp->mtpos; + + /* Zero counters if position is undefined. */ + if (pp->filno < 1 || pp->recno < 1) { + pp->nfiles = 0; + pp->tapeused = 0; + } + + /* Zero counters if new tape? */ + if (mp->acmode == WRITE_ONLY && (*newfile == 0 || *newfile == 1)) { + pp->nfiles = 0; + pp->tapeused = 0; + } + + /* Zero tapeused counter if rewinding and nfiles is still unknown. */ + if (pp->nfiles == 0 && *newfile == 1) + pp->tapeused = 0; + + /* Status output. */ + zmtdbgn (mp, "devtype = %s", mp->mtdev.devtype); + zmtdbgn (mp, "tapetype = %s", mp->mtdev.tapetype); + zmtdbgn (mp, "tapesize = %d", mp->mtdev.tapesize); + zmtdbgn (mp, "density = %s", + mp->mtdev.density[0] ? mp->mtdev.density : "na"); + zmtdbgn (mp, "blksize = %d", mp->mtdev.blksize); + zmtdbgn (mp, "acmode = %s", mp->acmode == READ_ONLY ? "read" : + ((*newfile < 0) ? "append" : "write")); + zmtdbgn (mp, "file = %d%s", pp->filno, ateot(pp) ? " (EOT)" : ""); + zmtdbgn (mp, "record = %d", pp->recno); + zmtdbgn (mp, "nfiles = %d", pp->nfiles); + zmtdbgn (mp, "tapeused = %d", pp->tapeused); + zmtfls (mp); + + /* Position to the desired file. Do not move the tape if newfile=0 + * or if NP (no-position) is specified for the device. Rewind the tape + * to get to a known position if current tape position is undefined. + */ + if (*newfile == 0 || (mp->flags & NP)) { + zmtdbg (mp, "file positioning is disabled\n"); + zmtfls (mp); + } + if (*newfile) { + /* Rewind if current position uncertain. */ + if ((mp->flags & RO) || pp->filno < 1 || pp->recno < 1) { + if (!(mp->flags & NP)) + if ((fd = zmtgetfd (mp)) == ERR || zmtrew(fd) == ERR) + goto err; + pp->filno = pp->recno = 1; + } + + /* Position to the desired file. NP disables file positioning, + * in which case we assume the user knows what they are doing + * and we are already there. + */ + if (mp->flags & NP) + *newfile = (*newfile < 0) ? pp->filno : *newfile; + else if ((*newfile = zmtfpos (mp, *newfile)) == XERR) + goto err; + } + + /* Reopen file with write permission if necessary. */ + if (mp->acmode == WRITE_ONLY) { + if (*chan) { + zmtclose (*chan); + zmtdbg (mp, "reopen for writing\n"); + } + (*chan) = fd = zmtopen (mp->iodev, WRONLY); + if (fd != ERR) + zmtdbgn (mp, + "device %s opened on descriptor %d\n", mp->iodev, fd); + } else + fd = zmtgetfd (mp); + + if (fd == ERR) + goto err; + set_mtdesc(fd,mp); + mp->errcnt = 0; + + zmtdbgn (mp, "file = %d%s", pp->filno, ateot(pp) ? " (EOT)" : ""); + zmtdbgn (mp, "record = %d", mp->mtpos.recno); + zmtfls (mp); + + if (mp->acmode == WRITE_ONLY) + zmtdbg (mp, "writing...\n"); + else + zmtdbg (mp, "reading...\n"); + + return (XOK); +err: + /* Error exit. */ + zmtfree (mp); + *chan = XERR; + + return (*chan); +} + + +/* ZZCLMT -- Close magtape. Write a new EOT mark at the current position + * if tape is open for writing, leaving tape positioned ready to write the + * next file. + */ +int +ZZCLMT (XINT *chan, XINT *devpos, XINT *o_status) +{ + register int fd; + register struct mtdesc *mp; + register struct _mtpos *pp; + int status, eof_seen, eor_seen, eot_seen; + + /* Since open files are closed during error recovery and an interrupt + * can occur while closing a magtape file, it is possible that ZZCLMT + * twice, after the host file has been closed and the MP descriptor + * freed. Watch out for a bad channel number or mp=NULL. + */ + *o_status = XERR; + if ((fd = *chan) <= 0) + return (XERR); + if ((mp = get_mtdesc(fd)) == NULL) + return (XERR); + pp = &mp->mtpos; + + eof_seen = 0; + eor_seen = 0; + eot_seen = 0; + status = OK; + + /* Close file and update tape position. + */ + if (mp->acmode == READ_ONLY) { + /* Rewind if the rewind-after-read flag is set. This is used on + * devices that can leave the tape in an undefined position after + * a file read. + */ + if (mp->flags & RR) { + if (zmtrew(fd) == ERR) + status = ERR; + else { + pp->filno = pp->recno = 1; + zmtdbgn (mp, "file = %d", pp->filno); + zmtdbgn (mp, "record = %d", pp->recno); + } + } + + /* Close device. */ + status = zmtclose (fd); + if (mp->flags & CE) + status = 0; + + /* On some SysV systems closing a tape opened RO causes a skip + * forward to BOF of the next file on the tape. This does not + * occur though when the device is closed after reading EOF on + * a file. + */ + if ((mp->flags & FC) && pp->recno > 1) + eof_seen = 1; + + } else if (pp->recno > 1) { + /* Close WRONLY other than at BOF always writes EOT, advancing + * the file position by one file. + */ + status = zmtclose (fd); + eof_seen = 1; + eot_seen = 1; + + } else if (mp->flags & WC) { + /* If a tape is opened for writing and then closed without any + * data being written, a tape mark is written resulting in a + * dummy EOT in the middle of the tape if writing continues. + * Backspace over the the extra tape mark if possible, otherwise + * write a short record. This will result in an extra dummy + * file being written to the tape but the only alternative is to + * rewind and space forward, or abort on an error. + */ + register int flags = mp->flags; + int blksize = mp->mtdev.blksize; + int bufsize; + char *bufp; + + if ((flags & NB) || ((flags & BO) && !(flags & RF))) { + bufsize = blksize ? blksize : SHORTREC; + bufsize = max (bufsize, SHORTREC); + if ((bufp = malloc(bufsize)) == NULL) { + zmtclose (fd); + status = ERR; + } else { + zmtdbg (mp, "no data - null file written\n"); + zmtfls (mp); + strcpy (bufp, "[NULLFILE]"); + write (fd, bufp, bufsize); + free (bufp); + status = zmtclose (fd); + eof_seen = 1; + } + } else { + /* Close and write EOT, reopen RDONLY and backspace over it. */ + status = (zmtclose(fd) == ERR); + if (status || (fd = zmtopen (mp->iodev, RDONLY)) == ERR) + status = ERR; + else { + status = ((flags & RF) ? zmtbsr : zmtbsf)(fd, 1); + status = (zmtclose(fd) == ERR) ? ERR : status; + } + eof_seen = 1; + } + eot_seen = 1; + } else { + status = zmtclose (fd); + eof_seen = 0; + eot_seen = 1; + } + + /* Update position information and write status output. */ + pp->pflags = status ? MF_ERR : 0; + if (eot_seen) { + pp->pflags |= MF_EOT; + } + if (eof_seen) { + pp->pflags |= MF_EOF; + pp->filno++; + pp->recno = 1; + if (mp->acmode == WRITE_ONLY) { + pp->nfiles = pp->filno - 1; + zmtdbgn (mp, "nfiles = %d", pp->nfiles); + } + if (mp->acmode == WRITE_ONLY || spaceused(pp)) + mp->tbytes += mp->mtdev.eofsize; + zmtdbgn (mp, "record = %d", pp->recno); + zmtdbgn (mp, "file = %d%s", pp->filno, ateot(pp) ? " (EOT)" : ""); + } + if (eor_seen) { + pp->pflags |= MF_EOR; + pp->recno++; + if (mp->acmode == WRITE_ONLY || spaceused(pp)) + mp->tbytes += mp->mtdev.gapsize; + zmtdbgn (mp, "record = %d", pp->recno); + } + + pp->tapeused += ((mp->tbytes + 512) / 1024); + zmtdbgn (mp, "tapeused = %d", pp->tapeused); + zmtfls (mp); + + *((struct _mtpos *)devpos) = *pp; + *o_status = status ? XERR : XOK; + zmtfree (mp); + + return (status); +} + + +/* ZZRDMT -- Read next tape record. We are supposed to be asynchronous, + * so save read status for return by next call to ZZWTMT. Read returns + * zero byte count if EOF is seen, as required by the specs, so we need + * do nothing special in that case. Tape is left positioned just past the + * tape mark. + */ +int +ZZRDMT ( + XINT *chan, + XCHAR *buf, + XINT *maxbytes, + XLONG *offset /* fixed block devices only */ +) +{ + register int fd = *chan, mb = (int)*maxbytes; + register struct mtdesc *mp = get_mtdesc(fd); + register struct _mtpos *pp = &mp->mtpos; + int status; + + if (mp->mtdev.blksize && (mb % mp->mtdev.blksize)) { + zmtdbgn (mp, + "read request %d not a multiple of device block size\n", mb); + zmtfls (mp); + } + + /* Position to the desired record (fixed block devices only). */ +/* + if (mp->mtdev.blksize && *offset > 0) { + int blkno, oldblk; + blkno = *offset / mp->mtdev.blksize + 1; + oldblk = mp->mtpos.recno; + if (blkno != oldblk) { + zmtdbgn (mp, "position to block %d\n", blkno); + if (blkno > oldblk) { + if (zmtfsr (fd, blkno - oldblk) == ERR) { + status = ERR; + goto done; + } + } else { + if ((mp->flags & NB) || zmtbsr(fd,oldblk-blkno) == ERR) { + status = ERR; + goto done; + } + } + mp->mtpos.recno = blkno; + } + } + */ + + /* Map read error to EOF if RE is set and we are reading the first + * record of a file (i.e. are positioned to EOT) or if IR is set. + */ + status = read (fd, (char *)buf, mb); + if (status == ERR) { + if ((mp->flags & RE) && pp->recno == 1) { + status = 0; + } else if (mp->flags & IR) { + zmtdbg (mp, "read error converted to zero read (EOF)\n"); + zmtfls (mp); + status = 0; + } + } + + /* If an error occurs on the read we assume that the tape has advanced + * beyond the bad record, and that the next read will return the next + * record on the tape. If this is not true and a read error loop + * occurs, we try skipping a record forward. If we continue to get + * read errors, we give up and return a premature EOF on the file. + */ + if (status == ERR) { + zmtdbgn (mp, "read error, errno = %d\n", errno); + zmtfls (mp); + if ((mp->errcnt)++ >= MAX_ERRCNT) + status = 0; /* give up; return EOF */ + else if ((mp->flags & SK) || mp->errcnt >= MAX_ERRIGNORE) + zmtfsr (fd, 1); + } + + mp->nbytes = status; + if (status >= 0 && mp->mtdev.recsize != status) + zmtdbgn (mp, "recsize = %d", mp->mtdev.recsize = status); + zmtfls (mp); + + return (status); +} + + +/* ZZWRMT -- Write next tape record. We are supposed to be asynchronous, + * so save write status for return by next call to ZZWTMT. + */ +int +ZZWRMT ( + XINT *chan, + XCHAR *buf, + XINT *nbytes, + XLONG *offset /* ignored on a write */ +) +{ + register int fd = *chan, nb = *nbytes; + register struct mtdesc *mp = get_mtdesc(fd); + int blksize = mp->mtdev.blksize; + + /* If writing to a blocked device, promote partial blocks to a + * full device block. + */ + if (blksize > 0 && (nb % blksize)) { + nb += blksize - (nb % blksize); + zmtdbgn (mp, "partial record promoted from %d to %d bytes\n", + *nbytes, nb); + } + + if (mp->mtdev.recsize != nb) + zmtdbgn (mp, "recsize = %d", mp->mtdev.recsize = nb); + if ((mp->nbytes = write (fd, (char *)buf, nb)) != nb) { + zmtdbgn (mp, "write error, status=%d, errno=%d\n", + mp->nbytes, errno); + mp->nbytes = ERR; + } + zmtfls (mp); + + return (XOK); +} + + +/* ZZWTMT -- "Wait" for i/o transfer to complete, and return the number of + * bytes transferred or XERR. A read at EOF returns a byte count of zero. + */ +int +ZZWTMT ( + XINT *chan, + XINT *devpos, + XINT *o_status +) +{ + register int fd = *chan; + register struct mtdesc *mp = get_mtdesc(fd); + register struct _mtpos *pp = &mp->mtpos; + register int flags = mp->flags; + int status, eof_seen, eor_seen, eot_seen; + + eof_seen = 0; + eor_seen = 0; + eot_seen = 0; + status = OK; + + if ((status = mp->nbytes) == ERR) { /* i/o error */ + status = ERR; + } else if (status == 0) { /* saw EOF */ + if (pp->recno <= 1) { + /* A read of zero (EOF) at the beginning of a file signals + * EOT. The file number does not change. + */ + pp->nfiles = pp->filno - 1; + zmtdbgn (mp, "nfiles = %d", pp->nfiles); + zmtdbgn (mp, "file = %d%s", + pp->filno, ateot(pp) ? " (EOT)" : ""); + zmtfls (mp); + eot_seen = 1; + + /* If the device allows us to read past the second filemark + * (SE) we must backspace over the filemark or any further + * reads could result in tape runaway. + */ + if (flags & SE) { + if ((flags & NB) || ((flags & FC) && !(flags & RF))) { + /* Cannot backspace; must rewind and space forward. */ + if (zmtrew(fd) == ERR) + status = ERR; + else if (zmtfsf(fd,pp->filno-1) == ERR) + status = ERR; + } else { + /* BSR is preferable if we can use it. */ + if ((((flags & RF) ? zmtbsr : zmtbsf)(fd, 1)) < 0) + status = ERR; + } + } + } else + eof_seen = 1; + } else + eor_seen = 1; + + /* Update position records and output status info. */ + pp->pflags = (status < 0) ? MF_ERR : 0; + if (eot_seen) + pp->pflags |= MF_EOT; + if (eof_seen) { + pp->filno++; + pp->recno = 1; + pp->pflags |= MF_EOF; + zmtdbg (mp, "record = 1"); + if (spaceused(pp)) + mp->tbytes += mp->mtdev.eofsize; + zmtdbgn (mp, "file = %d%s", pp->filno, ateot(pp) ? " (EOT)" : ""); + } + if (eor_seen) { + pp->pflags |= MF_EOR; + if (mp->mtdev.blksize > 0) + pp->recno += (status / mp->mtdev.blksize); + else + pp->recno++; + if (spaceused(pp)) + mp->tbytes += mp->mtdev.gapsize; + zmtdbgn (mp, "record = %d", pp->recno); + } + + if (status >= 0 && spaceused(pp)) { + mp->tbytes += status; + pp->tapeused += mp->tbytes / 1024; + mp->tbytes %= 1024; + zmtdbgn (mp, "tapeused = %d", pp->tapeused); + } + + *((struct _mtpos *)devpos) = *pp; + *o_status = (status < 0) ? XERR : status; + zmtfls (mp); + + return (status); +} + + +/* ZZSTMT -- Query a device or device driver parameter. + */ +int +ZZSTMT (XINT *chan, XINT *param, XLONG *lvalue) +{ + register int fd = *chan; + register struct mtdesc *mp = get_mtdesc(fd); + /* register struct _mtpos *pp = &mp->mtpos; */ + + + switch (*param) { + case FSTT_BLKSIZE: + /* Zero for variable size record devices, nonzero for fixed + * block size devices. + */ + (*lvalue) = mp->mtdev.blksize; + break; + case FSTT_FILSIZE: + /* When reading there is no way to know the file size, so set + * it to the largest possible value to make all reads in bounds. + * When appending a file the file starts out zero length, so + * set the file size to zero for a write access. + */ + if (mp->acmode == READ_ONLY) + (*lvalue) = MAX_LONG; + else + (*lvalue) = 0; + break; + case FSTT_OPTBUFSIZE: + (*lvalue) = mp->mtdev.optrec; + break; + case FSTT_MAXBUFSIZE: + (*lvalue) = mp->mtdev.maxrec; + break; + default: + (*lvalue) = XERR; + } + + return (*lvalue); +} + + +/* ZZRWMT -- Rewind the tape. This routine is in principle asynchronous but + * this is not the case for most unix systems (unless the host driver does + * asynchronous rewind with synchronization internally). + * + * This routine is not part of the normal binary file driver. + */ +int +ZZRWMT ( + PKCHAR *device, /* device name */ + PKCHAR *devcap, /* tapecap entry for device */ + XINT *o_status +) +{ + register struct mtdesc *mp; + register int fd; + int status; + + /* Open the main device descriptor. */ + mp = zmtdesc ((char *)device, READ_ONLY, (char *)devcap, NULL); + if (mp == NULL) { + *o_status = ERR; + return (XERR); + } + + /* If a rewind-on-close device is defined for this device use that + * to do the rewind, otherwise open the no-rewind device RDONLY and do + * an explicit rewind. The RD device can also be used to avoid an + * error condition if the device does not support the MTREW ioctl. + */ + if (mp->flags & RD) { + if ((fd = zmtopen (mp->rw_device, RDONLY)) == ERR) + status = ERR; + else + status = zmtclose (fd); + } else { + if ((fd = zmtopen (mp->iodev, RDONLY)) == ERR) { + status = ERR; + } else if (mp->flags & FC) { + /* Device does a FSF when closed read-only, making it + * impossible to leave the tape rewound after the close. + * Return ERR to cause MTIO to mark the position undefined, + * forcing a rewind the next time the tape is opened for i/o. + */ + static FILE *tty = NULL; + if (!tty && (tty = fopen (CONSOLE, "a")) != NULL) { + fprintf (tty, "cannot rewind device %s: ", (char *)device); + fprintf (tty, "add RD capability to dev$devices entry\n"); + fclose (tty); + } + status = ERR; + } else { + /* Normal rewind. */ + status = zmtrew (fd); + status = zmtclose(fd) ? ERR : status; + } + } + + zmtdbg (mp, "file = 1"); + zmtdbg (mp, "record = 1"); + zmtfls (mp); + *o_status = status ? XERR : XOK; + zmtfree (mp); + + return (status); +} + + + +/* + * INTERNAL INTERFACE ROUTINES. + * ---------------------------- + */ + +/* ZMTGETFD -- Open tape read-only, if not already open, and return the + * file descriptor as the function value. If the tape is already open + * the only action is to return the file descriptor. This routine is used + * to delay the device open during file positioning operations, so that + * it can be skipped if it is not necessary to move the tape. + */ +static int +zmtgetfd (mp) +register struct mtdesc *mp; +{ + register int fd; + + if (*mp->chan > 0) + return (*mp->chan); + + *mp->chan = fd = zmtopen (mp->iodev, RDONLY); + if (fd >= MAXOFILES) { + zmtclose (fd); + fd = ERR; + } + + if (fd == ERR) + zmtdbgn (mp, "failed to open device %s\n", mp->iodev); + else { + zmtdbgn (mp, "device %s opened on descriptor %d\n", mp->iodev, fd); + set_mtdesc (fd, mp); + mp->errcnt = 0; + mp->tbytes = 0; + } + + return (fd); +} + + +/* ZMTOPEN -- Convert the magtape device name into a unix pathname and open + * the drive. Do not move the tape. + * + * Devices can be specified as + * + * devname system device name in /dev or /dev/rmt + * /devpath full pathname of device + * ~/devpath user home directory relative pathname + * + * Returns the unix file descriptor or ERR. + */ +static int +zmtopen ( + char *dev, /* device name or pathname */ + int u_acmode /* read only or write only for tapes */ +) +{ + char path[SZ_PATHNAME+1]; + int fd = ERR; + + /* If the device name is already a pathname leave it alone, else + * prepend the /dev/ or /dev/rmt prefix. The device file can be + * in the user's home directory if ~/dev is specified. + */ + if (dev[0] == '/') { + /* Full pathname. */ + fd = open (dev, u_acmode); + + } else if (dev[0] == '~' && dev[1] == '/') { + /* User home directory relative pathname. */ + struct passwd *pwd; + pwd = getpwuid (getuid()); + if (pwd != NULL) { + strcpy (path, pwd->pw_dir); + strcat (path, &dev[1]); + endpwent(); + fd = open (path, u_acmode); + } + } else { + /* System device. */ + strcpy (path, "/dev/"); + strcat (path, dev); + if ((fd = open (path, u_acmode)) == ERR) { + /* If that fails take a look in /dev/rmt too, since this + * is where some SysV systems like to hide raw magtape device + * files. + */ + strcpy (path, "/dev/rmt/"); + strcat (path, dev); + fd = open (path, u_acmode); + } + } + + return (fd); +} + + +/* ZMTCLOSE -- Close a magtape device. + */ +static int zmtclose (int fd) +{ + register struct mtdesc *mp = get_mtdesc(fd); + zmtdbg (get_mtdesc(fd), "close device\n"); + zmtfls (mp); + return (close (fd)); +} + + +/* ZMTDESC -- Allocate and initialize the main magtape device descriptor. + */ +static struct mtdesc * +zmtdesc ( + char *device, /* host device to be used for i/o */ + int acmode, /* iraf file access mode code */ + char *devcap, /* tapecap entry for device */ + struct _mtpos *devpos /* device position info (or NULL ptr) */ +) +{ + register struct mtdesc *mp; + register struct mtdev *dp; + register struct mtchar *pp; + register char *ip, *op; + int pname; + + /* Allocate and initialize the device descriptor. */ + if ((mp = (struct mtdesc *) calloc (1, sizeof(*mp))) == NULL) + return (NULL); + + dp = &mp->mtdev; + dp->maxrec = MAXREC; + dp->optrec = OPTREC; + strcpy (dp->devtype, "generic"); + strcpy (dp->tapetype, "unknown"); + + mp->acmode = acmode; + strcpy (mp->iodev, device); + mp->mtioctop = MTIOCTOP; + mp->mtbsr = MTBSR; + mp->mtbsf = MTBSF; + mp->mtfsr = MTFSR; + mp->mtfsf = MTFSF; + mp->mtrew = MTREW; + + if (devpos) { + mp->mtpos = *devpos; + mp->mtpos.pflags = 0; + } + + /* Prepare to scan tapecap entry. */ + for (pp=devpar; pp->pname; pp++) + pp->valset = 0; + + /* Process the tapecap entry. This is a sequence of entries of the + * form "nn=value", where the NN is a two character name, the "=" is + * actually either `=' or `#', and where successive entries are + * delimited by colons. For example, ":dv=nrst0:rd=rst0:bs#0:...". + */ + for (ip=devcap; *ip; ) { + while (*ip && *ip != ':') + ip++; + if (*ip == ':') + ip++; + else + break; + pname = PNAME(ip[0],ip[1]); + + ip += 2; + if (*ip == '=' || *ip == '#') + ip++; + + for (pp=devpar; pp->pname; pp++) { + if (pp->pname == pname) { + /* If multiple entries are given for the parameter ignore + * all but the first. + */ + if (pp->valset) + continue; + else + mp->flags |= pp->bitflag; + + /* Check for a negated entry (e.g., ":ir@:"). */ + if (*ip == '@') { + mp->flags &= ~pp->bitflag; + pp->valset++; + continue; + } + + /* Check for a string valued parameter. */ + switch (pp->pcode) { + case P_DV: op = mp->nr_device; break; + case P_RD: op = mp->rw_device; break; + case P_DN: op = mp->mtdev.density; break; + case P_DT: op = mp->mtdev.devtype; break; + case P_TT: op = mp->mtdev.tapetype; break; + case P_SO: op = mp->mtdev.statusdev; break; + default: op = NULL; + } + + if (op != NULL) { + int nchars; + + /* String valued parameters. */ + for (nchars=0; *ip && *ip != ':'; ip++, nchars++) { + if (*ip == '\\' && isdigit(*(ip+1))) { + int n, i; + for (n=i=0; i < 3; i++) + n = n * 10 + (*(++ip) - '0'); + *op++ = n; + } else + *op++ = *ip; + } + *op = EOS; + pp->valset++; + + /* Default if no string value given but entry was + * found, e.g., ":so:". + */ + if (!nchars) + if (pp->pcode == P_SO) + strcpy (mp->mtdev.statusdev, ","); + break; + + } else if (*ip != ':') { + /* Numeric parameters. */ + int n = 0; + + while (*ip && *ip != ':') { + if (isdigit (*ip)) + n = n * 10 + (*ip - '0'); + ip++; + } + + switch (pp->pcode) { + case P_CT: mp->mtioctop = n; break; + case P_BF: mp->mtbsf = n; break; + case P_BR: mp->mtbsr = n; break; + case P_FF: mp->mtfsf = n; break; + case P_FR: mp->mtfsr = n; break; + case P_RI: mp->mtrew = n; break; + + case P_BS: dp->blksize = n; break; + case P_FS: dp->eofsize = n; break; + case P_MR: dp->maxrec = n; break; + case P_NF: dp->maxbsf = n; break; + case P_OR: dp->optrec = n; break; + case P_RS: dp->gapsize = n; break; + case P_TS: dp->tapesize = n; break; + default: /* ignore (bitflags) */ + ; + } + + pp->valset++; + break; + } + } + } + } + + /* Apply some obvious constraints. */ + if (dp->blksize) { + dp->maxrec = dp->maxrec / dp->blksize * dp->blksize; + dp->optrec = dp->optrec / dp->blksize * dp->blksize; + } + if (dp->maxrec > 0 && dp->optrec > dp->maxrec) + dp->optrec = dp->maxrec; + + zmtdbgopen (mp); + return (mp); +} + + +/* ZMTFREE -- Free the magtape device descriptor. + */ +static void +zmtfree (struct mtdesc *mp) +{ + zmtdbgclose (mp); + free (mp); +} + + +/* ZMTFPOS -- Position to the indicated file. The first file is #1. + * A negative newfile number signifies EOT. + */ +static int +zmtfpos ( + register struct mtdesc *mp, + int newfile /* file we want to position to */ +) +{ + register struct _mtpos *pp = &mp->mtpos; + register int flags = mp->flags; + int oldfile, oldrec, maxrec; + char *buf = NULL; + int fd, status, n; + + oldfile = pp->filno; + oldrec = pp->recno; + + if (newfile > 0) + zmtdbgn (mp, "position to file %d\n", newfile); + else if (newfile < 0) + zmtdbg (mp, "position to end of tape\n"); + else + return (oldfile); + + /* If we are positioning to EOT and UE is not set to force a search + * for EOT, use the nfiles information in the position descriptor to + * position to just before the EOT marker. + */ + if (newfile < 0 && !(flags&UE) && pp->nfiles > 0) { + newfile = pp->nfiles + 1; + zmtdbgn (mp, "end of tape is file %d\n", newfile); + } + zmtfls (mp); + + /* Don't do anything if already positioned to desired file and no + * further positioning is necessary. + */ + if (newfile == oldfile && oldrec == 1 && (!(flags & OW) || + newfile < pp->nfiles + 1 || mp->acmode == READ_ONLY)) + return (newfile); + + /* It is necessary to move the tape. Open the device if it has + * not already been opened. + */ + if ((fd = zmtgetfd(mp)) < 0) + return (ERR); + + /* Move the tape. */ + if (newfile == 1) { + if (zmtrew(fd) < 0) + return (ERR); + + } else if (newfile <= oldfile && newfile > 0) { + /* Backspace to the desired file. */ + if ((flags & NB) || + ((flags & NF) && oldfile - newfile > mp->mtdev.maxbsf)) { + + /* Device cannot backspace or is slow to backspace; must + * rewind and space forward. + */ + if (zmtrew(fd) < 0) + return (ERR); + oldfile = oldrec = 1; + zmtdbgn (mp, "file = %d", oldfile); + zmtfls (mp); + goto fwd; + } else if (flags & BO) { + /* BSF positions to BOF. */ + if (zmtbsf (fd, oldfile - newfile) < 0) + return (ERR); + } else { + /* BSF positions to BOT side of filemark. */ + if (zmtbsf (fd, oldfile - newfile + 1) < 0) + return (ERR); + else if (zmtfsf (fd, 1) < 0) + return (ERR); + } + + } else if (newfile < 0 && !(flags & UE) && + (pp->nfiles > 0 && oldfile == pp->nfiles+1)) { + + /* Already at EOT. */ + newfile = oldfile; + if ((flags & OW) && mp->acmode == WRITE_ONLY) + goto oweot; + + } else { + /* Space forward to desired file or EOT. + */ +fwd: + /* Fast file skip forward to numbered file. Used only when + * positioning to a numbered file, as opposed to positioning + * to EOT and the number of files on the tape is unknown. + * A multifile FSF is much faster on some devices than skipping + * a file at a time. It is also an atomic, uninterruptable + * operation so may be undesirable on devices where file + * positioning takes a long time, and could result in tape + * runaway in an attempt to position beyond EOT (if the host + * device driver cannot detect this). Fast skip is enabled if + * the MF (multifile-file) flag is set. + */ + if (newfile > oldfile && (flags & MF)) { + if (zmtfsf (fd, newfile - oldfile) < 0) + return (ERR); + + oldfile = newfile; + if ((flags & OW) && mp->acmode == WRITE_ONLY) + goto oweot; + else + goto done; + } + + /* Get a read buffer as large as the largest possible record, + * for variable record size devices, or the size of a device + * block for fixed block devices. + */ + if (mp->mtdev.blksize) + maxrec = mp->mtdev.blksize; + else { + maxrec = mp->mtdev.maxrec; + if (maxrec <= 0) + maxrec = MAXREC; + } + if (buf == NULL && !(buf = malloc(maxrec))) + return (ERR); + + /* Skip file forward one file at a time. This is tricky as we + * must be able to detect EOT when spacing forward or we risk + * tape runaway. Detecting EOT portably requires looking for + * a double EOT. We FSF to the next file and then read the + * first record; a read of zero (or ERR on some devices) signals + * a tape mark and hence double EOF or EOT. + */ + while (oldfile < newfile || newfile < 0) { + /* Test if the next record is a data record or filemark. */ + n = read (fd, buf, maxrec); + + /* Check for EOT, signified by two consecutive logical + * filemarks, or equivalently, a zero length file. On + * some systems a read at EOT might be an error, so treat + * a read error the same as EOF if the RE capability is set. + * (the IR flag causes all read errors to be treated as EOF + * and is intended only to try to workaround host driver bugs). + */ + if (n < 0 && !(flags & (RE|IR))) { + goto err; + } else if (n <= 0 && oldrec == 1) { + /* At EOT. Leave the tape between the filemarks if such + * a concept applies to the current device. If SE is + * not specified for the device, we are already there. + */ + + pp->nfiles = (newfile=oldfile) - 1; + zmtdbgn (mp, "nfiles = %d", pp->nfiles); + zmtdbg (mp, "at end of tape\n"); + zmtfls (mp); + + if (flags & SE) { + /* Cannot backspace? */ + if (flags & NB) { + newfile = oldfile; + if (zmtrew (fd) < 0) + goto err; + if (zmtfsf (fd, newfile - 1) < 0) + goto err; + oldrec = 1; + break; + } else { + if ((((flags & RF) ? zmtbsr:zmtbsf)(fd, 1)) < 0) + goto err; + } + } +oweot: + /* On some devices, e.g., 1/2 inch reel tape, the space + * between the two filemarks marking EOT can be large and + * we can get more data on the tape if we back up over the + * first filemark and then space forward over it, leaving + * the tape just after the first filemark rather than just + * before the second one. The OW (overwrite) capability + * enables this. + */ + if ((flags & OW) && !(flags & NB) && + mp->acmode == WRITE_ONLY) { + + if (flags & RF) { + status = zmtbsr (fd, 1); + status = (zmtfsr (fd, 1) < 0) ? ERR : status; + } else if (flags & BO) { + /* This may not actually do anything, depending + * upon the host driver... */ + status = zmtbsf (fd, 0); + } else { + status = zmtbsf (fd, 1); + status = (zmtfsf (fd, 1) < 0) ? ERR : status; + } + if (status < 0) + goto err; + } + + break; + + } else if (n > 0) { + if (zmtfsf (fd, 1) < 0) { +err: free (buf); + return (ERR); + } + } + + oldfile++; + oldrec = 1; + zmtdbgn (mp, "file = %d", oldfile); + zmtfls (mp); + } + + /* Set newfile to the file we actually ended up positioned to. */ + newfile = oldfile; + free (buf); + } +done: + /* Update position descriptor */ + pp->filno = newfile; + pp->recno = 1; + + return (newfile); +} + + +/* ZMTREW -- Rewind the tape. + */ +static int +zmtrew (int fd) +{ + register struct mtdesc *mp = get_mtdesc(fd); + struct mtop mt_rewind; + int status; + + mt_rewind.mt_op = mp->mtrew; + mt_rewind.mt_count = 1; + + zmtdbg (mp, "rewinding..."); + zmtfls (mp); + status = ioctl (fd, mp->mtioctop, (char *)&mt_rewind); + zmtdbgn (mp, "%s\n", status < 0 ? "failed" : "done"); + zmtfls (mp); + + return (status); +} + + +/* ZMTFSF -- Skip file forward. + */ +static int +zmtfsf (int fd, int nfiles) +{ + register struct mtdesc *mp = get_mtdesc(fd); + struct mtop mt_fwdskipfile; + int status; + + mt_fwdskipfile.mt_op = mp->mtfsf; + mt_fwdskipfile.mt_count = nfiles; + + zmtdbgn (mp, "skip %d file%s forward...", nfiles, + nfiles > 1 ? "s" : ""); + zmtfls (mp); + status = ioctl (fd, mp->mtioctop, (char *)&mt_fwdskipfile); + zmtdbgn (mp, "%s\n", status < 0 ? "failed" : "done"); + zmtfls (mp); + + return (status); +} + + +/* ZMTBSF -- Skip file backward. + */ +static int +zmtbsf (int fd, int nfiles) +{ + register struct mtdesc *mp = get_mtdesc(fd); + struct mtop mt_backskipfile; + int status; + + mt_backskipfile.mt_op = mp->mtbsf; + mt_backskipfile.mt_count = nfiles; + + zmtdbgn (mp, "skip %d file%s backward...", nfiles, + nfiles > 1 ? "s" : ""); + zmtfls (mp); + status = ioctl (fd, mp->mtioctop, (char *)&mt_backskipfile); + zmtdbgn (mp, "%s\n", status < 0 ? "failed" : "done"); + zmtfls (mp); + + return (status); +} + + +/* ZMTFSR -- Skip record forward. + */ +static int +zmtfsr (int fd, int nrecords) +{ + register struct mtdesc *mp = get_mtdesc(fd); + struct mtop mt_fwdskiprecord; + int status; + + mt_fwdskiprecord.mt_op = mp->mtfsr; + mt_fwdskiprecord.mt_count = nrecords; + + zmtdbgn (mp, "skip %d record%s forward...", nrecords, + nrecords > 1 ? "s" : ""); + zmtfls (mp); + status = ioctl (fd, mp->mtioctop, (char *)&mt_fwdskiprecord); + zmtdbgn (mp, "%s\n", status < 0 ? "failed" : "done"); + zmtfls (mp); + + return (status); +} + + +/* ZMTBSR -- Skip record backward. + */ +static int +zmtbsr (int fd, int nrecords) +{ + register struct mtdesc *mp = get_mtdesc(fd); + struct mtop mt_backskiprecord; + int status; + + mt_backskiprecord.mt_op = mp->mtbsr; + mt_backskiprecord.mt_count = nrecords; + + zmtdbgn (mp, "skip %d record%s backward...", nrecords, + nrecords > 1 ? "s" : ""); + zmtfls (mp); + status = ioctl (fd, mp->mtioctop, (char *)&mt_backskiprecord); + zmtdbgn (mp, "%s\n", status < 0 ? "failed" : "done"); + zmtfls (mp); + + return (status); +} + + +/* + * I/O logging routines. + * + * zmtdbgopen (mp) + * zmtdbg (mp, msg) + * zmtdbgn (mp, fmt, arg) + * zmtdbgn (mp, fmt, arg1, arg2) + * zmtdbg3 (mp, fmt, arg1, arg2, arg3) + * zmtfls (mp) + * zmtdbgclose (mp) + * + * Output may be written to either a file, if an absolute file pathname is + * given, or to a socket specified as "host[,port]". + */ + +#ifdef TCPIP +static SIGFUNC sigpipe = NULL; +static int nsockets = 0; +static int s_port[MAXDEV]; +static FILE *s_fp[MAXDEV]; +static int s_fd[MAXDEV]; +static int s_checksum[MAXDEV]; +#endif + +/* ZMTDBGOPEN -- Attempt to open a file or socket for status logging. + */ +static void zmtdbgopen (struct mtdesc *mp) +{ +#ifndef TCPIP + if (!mp->mtdev.statusdev[0] || mp->mtdev.statusout) + return; + if (mp->mtdev.statusdev[0] == '/') + mp->mtdev.statusout = fopen (mp->mtdev.statusdev, "a"); +#else + register char *ip, *op; + struct sockaddr_in sockaddr; + int port, isfile, sum, s, i; + char host[SZ_FNAME]; + struct hostent *hp; + FILE *fp = (FILE *) NULL; + + + /* Status logging disabled. */ + if (!mp->mtdev.statusdev[0]) + return; + + /* Status device already open. This is only possible in repeated + * calls to zmtdbgopen after a zzopmt. + */ + if (mp->mtdev.statusout) { + if (nsockets > 0 && !sigpipe) + sigpipe = (SIGFUNC) signal (SIGPIPE, SIG_IGN); + return; + } + + /* Compute statusdev checksum. */ + for (sum=0, ip = mp->mtdev.statusdev; *ip; ip++) + sum += (sum + *ip); + + /* Log status output to a file if a pathname is specified. + */ + for (isfile=0, ip=mp->mtdev.statusdev; *ip; ip++) + if ((isfile = (*ip == '/'))) + break; + if (isfile) { + mp->mtdev.statusout = fopen (mp->mtdev.statusdev, "a"); + if (mp->mtdev.statusout) { + for (i=0; i < MAXDEV; i++) + if (!s_fp[i]) { + s_fp[i] = mp->mtdev.statusout; + s_port[i] = s_fd[i] = 0; + s_checksum[i] = sum; + break; + } + } + return; + } + + /* If the entry is of the form "host" or "host,port" then status output + * is written to a socket connected to the specified host and port. + */ + for (ip=mp->mtdev.statusdev, op=host; *ip && *ip != ','; ) + *op++ = *ip++; + *op = EOS; + if (!host[0]) + strcpy (host, "localhost"); + if (*ip == ',') + ip++; + port = (isdigit(*ip)) ? atoi(ip) : DEFPORT; + + /* Is port already open in cache? */ + s = 0; + for (i=0; i < MAXDEV; i++) + if (s_port[i] == port) { + if (s_checksum[i] != sum) { + fclose (s_fp[i]); + s_fd[i] = s_port[i] = s_checksum[i] = 0; + s_fp[i] = NULL; + } else { + s = s_fd[i]; + fp = s_fp[i]; + } + break; + } + + if (!s) { + if ((hp = gethostbyname(host)) == NULL) + return; + if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) + return; + + bzero ((char *)&sockaddr, sizeof(sockaddr)); + bcopy ((char *)hp->h_addr,(char *)&sockaddr.sin_addr, hp->h_length); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = htons((short)port); + if (connect (s,(struct sockaddr *)&sockaddr,sizeof(sockaddr)) < 0) { + close (s); + return; + } + + fp = fdopen (s, "w"); + for (i=0; i < MAXDEV; i++) + if (!s_fp[i]) { + s_port[i] = port; + s_fd[i] = s; + s_fp[i] = fp; + s_checksum[i] = sum; + break; + } + } + + /* Ignore signal generated if server goes away unexpectedly. */ + nsockets++; + if (!sigpipe) + sigpipe = signal (SIGPIPE, SIG_IGN); + + mp->mtdev.statusout = fp; + zmtdbgn (mp, "iodev = %s", mp->iodev); + if (gethostname (host, SZ_FNAME) == 0) + zmtdbgn (mp, "host = %s", host); +#endif +} + + +/* ZMTDBGCLOSE -- Close the status output. Called at ZZCLMT time. If the + * status output is a socket merely flush the output and restore the original + * sigpipe signal handler if the reference count for the process goes to zero. + * If the output is a file always close the file. If the debug output is + * changed from a socket to a file during the execution of a process this + * will leave a socket open, never to be closed, but this is not likely to + * be worth fixing since the status output device, if used, should change + * infrequently. + */ +static void zmtdbgclose (struct mtdesc *mp) +{ + register int i; + + if (mp->mtdev.statusout) { + fflush (mp->mtdev.statusout); + for (i=0; i < MAXDEV; i++) { + if (s_fp[i] == mp->mtdev.statusout) { + if (s_port[i]) + nsockets--; + else { + fclose (mp->mtdev.statusout); + s_checksum[i] = 0; + s_fp[i] = NULL; + } + break; + } + } + + if (sigpipe && nsockets <= 0) { +#ifdef AUX + signal (SIGPIPE, (sigfunc_t)sigpipe); +#else + signal (SIGPIPE, sigpipe); +#endif + sigpipe = (SIGFUNC) NULL; + nsockets = 0; + } + } +} + +static void zmtdbg (struct mtdesc *mp, char *msg) +{ + register FILE *out; + register char *ip; + + if ((out = mp->mtdev.statusout)) { + for (ip=msg; *ip; ip++) { + if (*ip == '\n') { + putc ('\\', out); + putc ('n', out); + } else + putc (*ip, out); + } + putc ('\n', out); + } +} + +static void zmtfls (struct mtdesc *mp) +{ + FILE *out; + if ((out = mp->mtdev.statusout)) + fflush (out); +} + +static void zmtdbgn ( struct mtdesc *mp, const char *argsformat, ... ) +{ + va_list ap; + char obuf[SZ_LINE]; + va_start (ap, argsformat); + vsnprintf (obuf, SZ_LINE, argsformat, ap); + va_end (ap); + obuf[SZ_LINE-1]='\0'; + zmtdbg (mp, obuf); +} + + + +#else + +int ZZOPMT ( + PKCHAR *device, /* device name */ + XINT *acmode, /* access mode: read_only or write_only for tapes */ + PKCHAR *devcap, /* tapecap entry for device */ + XINT *devpos, /* pointer to tape position info struct */ + XINT *newfile, /* file to be opened or EOT */ + XINT *chan /* OS channel of opened file */ +) +{ + return (XERR); +} + + +int ZZCLMT (XINT *chan, XINT *devpos, XINT *o_status) +{ + return (XERR); +} + + +int ZZRDMT ( + XINT *chan, + XCHAR *buf, + XINT *maxbytes, + XLONG *offset /* fixed block devices only */ +) +{ + return (XERR); +} + + +int ZZWRMT ( + XINT *chan, + XCHAR *buf, + XINT *nbytes, + XLONG *offset /* ignored on a write */ +) +{ + return (XERR); +} + + +int ZZWTMT ( + XINT *chan, + XINT *devpos, + XINT *o_status +) +{ + return (XERR); +} + + +int ZZSTMT (XINT *chan, XINT *param, XLONG *lvalue) +{ + return (XERR); +} + + +int ZZRWMT ( + PKCHAR *device, /* device name */ + PKCHAR *devcap, /* tapecap entry for device */ + XINT *o_status +) +{ + return (XERR); +} + + +#endif + + diff --git a/unix/os/zfiond.c b/unix/os/zfiond.c new file mode 100644 index 00000000..6f12413f --- /dev/null +++ b/unix/os/zfiond.c @@ -0,0 +1,918 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LINUX +#include +#endif +#ifdef MACOSX +#include +#endif + +#include +#include + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_spp +#include + +/* + * ZFIOND -- This driver provides a FIO-compatible interface to network or + * IPC streaming devices such as Berkeley sockets, FIFOs, and the like. + * Any connection-oriented stream type network interface can be supported. + * + * The type of connection desired is determined at device open time by the + * "filename" and file access mode arguments. The syntax for the filename + * argument is as follows: + * + * :
[ : flag ] [ : flag...] + * + * where is one of "inet" (internet tcp/ip socket), "unix" (unix + * domain socket) or "fifo" (named pipe). The form of the address depends + * upon the domain, as illustrated in the examples below. + * + * inet:5187 Server connection to port 5187 on the local + * host. For a client, a connection to the + * given port on the local host. + * + * inet:5187:foo.bar.edu Client connection to port 5187 on internet + * host foo.bar.edu. The dotted form of address + * may also be used. + * + * unix:/tmp/.IMT212 Unix domain socket with the given pathname + * IPC method, local host only. + * + * fifo:/dev/imt1i:/dev/imt1o FIFO or named pipe with the given pathname. + * IPC method, local host only. Two pathnames + * are required, one for input and one for + * output, since FIFOs are not bidirectional. + * For a client the first fifo listed will be + * the client's input fifo; for a server the + * first fifo will be the server's output fifo. + * This allows the same address to be used for + * both the client and the server, as for the + * other domains. + * + * sock:5 Used by servers to accept a connection on + * a server socket opened in nonblocking mode + * on the given channel (5 in the example). + * The channel is returned in a previous call + * to open an INET or UNIX server port. + * + * The address field may contain up to two "%d" fields. If present, the + * user's UID will be substituted (e.g. "unix:/tmp/.IMT%d"). + * + * The protocol flags currently supported are "text", "binary", "nonblock", + * and "nodelay". If "text" is specified the datastream is assumed to + * consist only of byte packed ascii text and is automatically converted by + * the driver to and from SPP chars during i/o. The default is binary i/o + * (no conversions). The "nonblock" flag is used to specify nonblocking + * mode. The "nodelay" flag is used to return an error when opening a + * "sock" connection in "nonblock" mode and for read when there is no + * pending connection or data. + * + * + * Client connections normally use mode READ_WRITE, although READ_ONLY and + * WRITE_ONLY are permitted. APPEND is the same as WRITE_ONLY. A server + * connection is indicated by the mode NEW_FILE. The endpoints of the server + * connection will be created if necessary. A client connection will timeout + * if no server responds. + * + * By default a server connection will block until a client connects, and + * the channel returned will be the i/o channel for the client connection. + * If however a server connection is opened in nonblocking mode then a server + * socket will be opened which can be used for multiple client connections. + * When a client makes a connection attempt, opening the server socket as + * "sock:", where is the channel assigned to the socket, will + * accept the client connection and open a new channel to be used for + * bidirectional i/o to the client. The open will block until a client + * connects unless the socket is opened in nonblocking mode. If opening the + * channel under IRAF FIO and separate read and write streams are desired, + * this can be achieved by using REOPEN to open a second stream on the same + * channel). The server sees an EOF on the input stream when the client + * disconnects. + * + * The "nodelay" flag will poll for "sock" connections or for a pending + * read. This uses the SELECT call. If there is no pending connection + * then the open call will return an error through FIO. The application + * may trap the error and try again later. If there is no pending data in a + * read then the number of bytes read is set to XERR and FIO will return ERR. + * The exception to this is if asynchronous reads are used with AREADB + * and AWAITB. In this case the application will see the number of bytes + * read as zero for EOF (client disconnected) and ERR for no data. + * + * FIFO domain connection are slightly different. When the server opens a + * FIFO connection the open returns immediately. When the server reads from + * the input fifo the server will block until some data is written to the + * fifo by a client. The server connection will remain open over multiple + * client connections until it is closed by the server. This is done to + * avoid a race condition that could otherwise occur at open time, with both + * the client and the server blocked waiting for an open on the opposite stream. + */ + +#define SZ_NAME 256 +#define SZ_OBUF 4096 +#define MAXCONN 32 +#define MAXSEL 32 + +#define INET 1 +#define UNIX 2 +#define FIFO 3 +#define SOCK 4 + +#define F_SERVER 00001 +#define F_NONBLOCK 00002 +#define F_TEXT 00004 +#define F_DEL1 00010 +#define F_DEL2 00020 +#define F_NODELAY 00040 + +/* Network portal descriptor. */ +struct portal { + int channel; + int domain; + int flags; + int datain; + int dataout; + int keepalive; + char path1[SZ_NAME]; + char path2[SZ_NAME]; +}; + +#define get_desc(fd) ((struct portal *)zfd[fd].fp) +#define set_desc(fd,np) zfd[fd].fp = (FILE *)np +#define min(a,b) (((a)<(b))?(a):(b)) + +static jmp_buf jmpbuf; +static int jmpset = 0; +static int recursion = 0; +extern int errno; +static int getstr(); + +static void nd_onsig (int sig, int *arg1, int *arg2); + + + +/* ZOPNND -- Open a network device. + */ +int +ZOPNND ( + PKCHAR *pk_osfn, /* UNIX name of file */ + XINT *mode, /* file access mode */ + XINT *chan /* file number (output) */ +) +{ + register int fd; + register struct portal *np, *s_np = (struct portal *) NULL; + unsigned short host_port = 0; + unsigned long host_addr = 0; + char osfn[SZ_NAME*2]; + char flag[SZ_NAME]; + char *ip; + + /* Get network device descriptor. */ + if (!(np = (struct portal *) calloc (1, sizeof(struct portal)))) { + *chan = XERR; + return (XERR); + } + + /* Expand any %d fields in the network address to the UID. */ + sprintf (osfn, (char *)pk_osfn, getuid(), getuid()); + + /* Parse the network filename to determine the domain type and + * network address. + */ + if (strncmp (osfn, "inet:", 5) == 0) { + /* Internet connection. + */ + char port_str[SZ_NAME]; + char host_str[SZ_NAME]; + unsigned short port; + struct servent *sv; + struct hostent *hp; + + /* Get port number. This may be specified either as a service + * name or as a decimal port number. + */ + ip = osfn + 5; + if (getstr (&ip, port_str, SZ_NAME, ':') <= 0) + goto err; + if (isdigit (port_str[0])) { + port = atoi (port_str); + host_port = htons (port); + } else if ((sv = getservbyname(port_str,"tcp"))) { + host_port = sv->s_port; + } else + goto err; + + /* Get host address. This may be specified either has a host + * name or as an Internet address in dot notation. If no host + * name is specified default to the local host. + */ + if (getstr (&ip, host_str, SZ_NAME, ':') <= 0) + strcpy (host_str, "localhost"); + if (isdigit (host_str[0])) { + host_addr = inet_addr (host_str); + if ((int)host_addr == -1) + goto err; + } else if ((hp = gethostbyname(host_str))) { + bcopy (hp->h_addr, (char *)&host_addr, sizeof(host_addr)); + } else + goto err; + + np->domain = INET; + + } else if (strncmp (osfn, "unix:", 5) == 0) { + /* Unix domain socket connection. + */ + ip = osfn + 5; + if (!getstr (&ip, np->path1, SZ_NAME, ':')) + goto err; + np->domain = UNIX; + + } else if (strncmp (osfn, "sock:", 5) == 0) { + /* Open (accept) a client connection on an existing, open + * server socket. + */ + char chan_str[SZ_NAME]; + int channel; + + /* Get the channel of the server socket. */ + ip = osfn + 5; + if (getstr (&ip, chan_str, SZ_NAME, ':') <= 0) + goto err; + if (isdigit (chan_str[0])) + channel = atoi (chan_str); + else + goto err; + + /* Get the server portal descriptor. */ + s_np = get_desc(channel); + if (!(s_np->flags & F_SERVER)) + goto err; + + np->domain = SOCK; + + } else if (strncmp (osfn, "fifo:", 5) == 0) { + /* FIFO (named pipe) connection. + */ + ip = osfn + 5; + if (*mode == NEW_FILE) { + /* Server. */ + if (!getstr (&ip, np->path2, SZ_NAME, ':')) + goto err; + if (!getstr (&ip, np->path1, SZ_NAME, ':')) + goto err; + } else { + /* Client. */ + if (!getstr (&ip, np->path1, SZ_NAME, ':')) + goto err; + if (!getstr (&ip, np->path2, SZ_NAME, ':')) + goto err; + } + np->domain = FIFO; + + } else + goto err; + + /* Process any optional protocol flags. + */ + while (getstr (&ip, flag, SZ_NAME, ':') > 0) { + /* Get content type (text or binary). If the stream will be used + * only for byte-packed character data the content type can be + * specified as "text" and data will be automatically packed and + * unpacked during i/o. + */ + if (strcmp (flag, "text") == 0) + np->flags |= F_TEXT; + if (strcmp (flag, "binary") == 0) + np->flags &= ~F_TEXT; + + /* Check for nonblocking i/o or connections. */ + if (strcmp (flag, "nonblock") == 0) + np->flags |= F_NONBLOCK; + + /* Check for no delay flag. */ + if (strcmp (flag, "nodelay") == 0) + np->flags |= F_NODELAY; + } + + /* Open the network connection. + */ + switch (*mode) { + case READ_ONLY: + /* Client side read only FIFO connection. */ + if (np->domain == FIFO) { + if ((fd = open (np->path1, O_RDONLY|O_NDELAY)) != ERR) + fcntl (fd, F_SETFL, O_RDONLY); + np->datain = fd; + np->dataout = -1; + break; + } + /* fall through */ + + case WRITE_ONLY: + case APPEND: + /* Client side write only FIFO connection. */ + if (np->domain == FIFO) { + if ((fd = open (np->path2, O_WRONLY|O_NDELAY)) != ERR) + fcntl (fd, F_SETFL, O_WRONLY); + np->datain = -1; + np->dataout = fd; + break; + } + /* fall through */ + + case READ_WRITE: + if (np->domain == INET) { + /* Client side Internet domain connection. */ + struct sockaddr_in sockaddr; + + /* Get socket. */ + if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0) + goto err; + + /* Compose network address. */ + bzero ((char *)&sockaddr, sizeof(sockaddr)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = host_port; + bcopy ((char *)&host_addr, (char *)&sockaddr.sin_addr, + sizeof(host_addr)); + + /* Connect to server. */ + if (fd >= MAXOFILES || (connect (fd, + (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0)) { + close (fd); + fd = ERR; + } else { + np->datain = fd; + np->dataout = fd; + } + + } else if (np->domain == UNIX) { + /* Client side Unix domain socket connection. */ + struct sockaddr_un sockaddr; + + /* Get socket. */ + if ((fd = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) + goto err; + + /* Compose network address. */ + bzero ((char *)&sockaddr, sizeof(sockaddr)); + sockaddr.sun_family = AF_UNIX; + strncpy (sockaddr.sun_path, + np->path1, sizeof(sockaddr.sun_path)); + + /* Connect to server. */ + if (fd >= MAXOFILES || (connect (fd, + (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0)) { + close (fd); + fd = ERR; + } else { + np->datain = fd; + np->dataout = fd; + } + + } else if (np->domain == FIFO) { + /* Client side FIFO connection. */ + int fd1, fd2; + + /* Open the fifos. */ + if ((fd1 = open (np->path1, O_RDONLY|O_NDELAY)) != ERR) + fcntl (fd1, F_SETFL, O_RDONLY); + if ((fd2 = open (np->path2, O_WRONLY|O_NDELAY)) != ERR) + fcntl (fd2, F_SETFL, O_WRONLY); + + /* Clean up if there is an error. */ + if (fd1 < 0 || fd1 > MAXOFILES || fd2 < 0 || fd2 > MAXOFILES) { + if (fd1 > 0) + close (fd1); + if (fd2 > 0) + close (fd2); + fd = ERR; + } else { + np->datain = fd1; + np->dataout = fd2; + fd = fd1; + } + } else + goto err; + break; + + case NEW_FILE: + /* Connect to a client. */ + np->flags |= F_SERVER; + + if (np->domain == INET) { + /* Server side Internet domain connection. */ + struct sockaddr_in sockaddr; + int s, reuse=1; + + /* Get socket. */ + if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) + goto err; + + /* Bind server port to socket. */ + bzero ((char *)&sockaddr, sizeof(sockaddr)); + sockaddr.sin_family = AF_INET; + sockaddr.sin_port = host_port; + sockaddr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, + sizeof(reuse)) < 0) { + close (s); + goto err; + } + + if (bind (s, + (struct sockaddr *)&sockaddr, sizeof(sockaddr)) < 0) { + close (s); + goto err; + } + + /* Enable queuing of client connections. */ + if (listen (s, MAXCONN) < 0) { + close (s); + goto err; + } + + /* If in blocking mode wait for a client connection, otherwise + * return the server socket on the channel. + */ + if (!(np->flags & F_NONBLOCK)) { + if ((fd = accept (s, (struct sockaddr *)0, + (socklen_t *)0)) < 0) { + close (s); + goto err; + } else + close (s); + } else + fd = s; + + np->datain = fd; + np->dataout = fd; + + } else if (np->domain == UNIX) { + /* Server side Unix domain connection. */ + struct sockaddr_un sockaddr; + int addrlen, s; + + /* Get socket. */ + if ((s = socket (AF_UNIX, SOCK_STREAM, 0)) < 0) + goto err; + + /* Bind server port to socket. */ + bzero ((char *)&sockaddr, sizeof(sockaddr)); + sockaddr.sun_family = AF_UNIX; + strncpy (sockaddr.sun_path,np->path1,sizeof(sockaddr.sun_path)); + addrlen = sizeof(sockaddr) - sizeof(sockaddr.sun_path) + + strlen(np->path1); + + unlink (np->path1); + if (bind (s, (struct sockaddr *)&sockaddr, addrlen) < 0) { + close (s); + goto err; + } + + /* Enable queuing of client connections. */ + if (listen (s, MAXCONN) < 0) { + close (s); + goto err; + } + + /* If in blocking mode wait for a client connection, otherwise + * return the server socket on the channel. + */ + if (!(np->flags & F_NONBLOCK)) { + if ((fd = accept (s, (struct sockaddr *)0, + (socklen_t *)0)) < 0) { + close (s); + goto err; + } else + close (s); + } else + fd = s; + + np->datain = fd; + np->dataout = fd; + np->flags |= F_DEL1; + + } else if (np->domain == SOCK) { + /* Open (accept) a client connection on a server socket. */ + int s = s_np->channel; + + if (s_np->flags & F_NODELAY) { + struct timeval timeout; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + fd_set readfds; + FD_ZERO (&readfds); + FD_SET (s, &readfds); +#else + int readfds = (1 << s); +#endif + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select (MAXSEL, &readfds, NULL, NULL, &timeout)) { + if ((fd = accept (s, (struct sockaddr *)0, + (socklen_t *)0))<0) + goto err; + } else { + goto err; + } + } else { + if ((fd = accept (s, (struct sockaddr *)0, + (socklen_t *)0)) < 0) + goto err; + } + + np->datain = fd; + np->dataout = fd; + np->flags = s_np->flags; + + } else if (np->domain == FIFO) { + /* Server side FIFO connection. */ + int fd1=0, fd2=0, keepalive=0; + + /* Create fifos if necessary. */ + if (access (np->path1, 0) < 0) { + if (mknod (np->path1, 010660, 0) < 0) + goto err; + else + np->flags |= F_DEL1; + } + if (access (np->path2, 0) < 0) { + if (mknod (np->path2, 010660, 0) < 0) { + unlink (np->path1); + goto err; + } else + np->flags |= F_DEL2; + } + + /* Open the output fifo (which is the client's input fifo). + * We have to open it ourselves first as a client to get + * around the fifo open-no-client error. + */ + if ((fd1 = open (np->path2, O_RDONLY|O_NDELAY)) != -1) { + if ((fd2 = open (np->path2, O_WRONLY|O_NDELAY)) != -1) + fcntl (fd2, F_SETFL, O_WRONLY); + close (fd1); + } + + /* Open the input fifo. */ + if ((fd1 = open (np->path1, O_RDONLY|O_NDELAY)) == -1) + fprintf (stderr, "Warning: cannot open %s\n", np->path1); + else { + /* Clear O_NDELAY for reading. */ + fcntl (fd1, F_SETFL, O_RDONLY); + + /* Open the client's output fifo as a pseudo-client to + * make it appear that a client is connected. + */ + keepalive = open (np->path1, O_WRONLY); + } + + /* Clean up if there is an error. */ + if (fd1 < 0 || fd1 > MAXOFILES || fd2 < 0 || fd2 > MAXOFILES) { + if (fd1 > 0) { + close (fd1); + close (keepalive); + } + if (fd2 > 0) + close (fd2); + fd = ERR; + } else { + np->datain = fd1; + np->dataout = fd2; + np->keepalive = keepalive; + fd = fd1; + } + + } else + goto err; + break; + + default: + fd = ERR; + } + + /* Initialize the kernel file descriptor. Seeks are illegal for a + * network device; network devices are "streaming" files (blksize=1) + * which can only be accessed sequentially. + */ + if ((*chan = fd) == ERR) { +err: free (np); + *chan = XERR; + } else if (fd >= MAXOFILES) { + free (np); + close (fd); + *chan = XERR; + } else { + zfd[fd].fp = NULL; + zfd[fd].fpos = 0L; + zfd[fd].nbytes = 0; + zfd[fd].flags = 0; + zfd[fd].filesize = 0; + set_desc(fd,np); + np->channel = fd; + } + + return (*chan); +} + + +/* ZCLSND -- Close a network device. + */ +int +ZCLSND (XINT *fd, XINT *status) +{ + register struct portal *np = get_desc(*fd); + register int flags; + + if (np) { + flags = np->flags; + + if (np->datain > 0) + close (np->datain); + if (np->dataout > 0 && np->dataout != np->datain) + close (np->dataout); + if (np->keepalive > 0) + close (np->keepalive); + + if (flags & F_DEL1) + unlink (np->path1); + if (flags & F_DEL2) + unlink (np->path2); + + free (np); + set_desc(*fd,NULL); + *status = XOK; + + } else + *status = XERR; + + return (*status); +} + + +/* ZARDND -- "Asynchronous" binary block read. Initiate a read of at most + * maxbytes bytes from the file FD into the buffer BUF. Status is returned + * in a subsequent call to ZAWTND. + */ +int +ZARDND ( + XINT *chan, /* UNIX file number */ + XCHAR *buf, /* output buffer */ + XINT *maxbytes, /* max bytes to read */ + XLONG *offset /* 1-indexed file offset to read at */ +) +{ + register int n; + int fd = *chan; + struct fiodes *kfp = &zfd[fd]; + register struct portal *np = get_desc (fd); + register char *ip; + register XCHAR *op; + int nbytes, maxread; + struct timeval timeout; +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + fd_set readfds; + FD_ZERO (&readfds); + FD_SET (np->datain, &readfds); +#else + int readfds; +#endif + + /* Determine maximum amount of data to be read. */ + maxread = (np->flags & F_TEXT) ? *maxbytes/sizeof(XCHAR) : *maxbytes; + + /* The following call to select shouldn't be necessary, but it + * appears that, due to the way we open a FIFO with O_NDELAY, read + * can return zero if read is called before the process on the other + * end writes any data. This happens even though fcntl is called to + * restore blocking i/o after the open. + */ +#if defined(POSIX) || defined(LINUX) || defined(MACOSX) + FD_ZERO (&readfds); + FD_SET (np->datain, &readfds); +#else + readfds = (1 << np->datain); +#endif + if ((np->flags & F_NODELAY) && np->datain < MAXSEL) { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (select (MAXSEL, &readfds, NULL, NULL, &timeout)) + nbytes = read (np->datain, (char *)buf, maxread); + else + nbytes = XERR; + } else { + if (np->domain == FIFO && np->datain < MAXSEL) { + select (MAXSEL, &readfds, NULL, NULL, NULL); + nbytes = read (np->datain, (char *)buf, maxread); + } else { + nbytes = read (np->datain, (char *)buf, maxread); + } + } + + if ((n = nbytes) > 0 && (np->flags & F_TEXT)) { + op = (XCHAR *) buf; + op[n] = XEOS; + for (ip = (char *)buf; --n >= 0; ) + op[n] = ip[n]; + nbytes *= sizeof(XCHAR); + } + + kfp->nbytes = nbytes; + + return (nbytes); +} + + +/* ZAWRND -- "Asynchronous" binary block write. Initiate a write of exactly + * nbytes bytes from the buffer BUF to the file FD. Status is returned in a + * subsequent call to ZAWTND. + */ +int +ZAWRND ( + XINT *chan, /* UNIX file number */ + XCHAR *buf, /* buffer containing data */ + XINT *nbytes, /* nbytes to be written */ + XLONG *offset /* 1-indexed file offset */ +) +{ + register int fd = *chan; + register struct fiodes *kfp = &zfd[fd]; + register struct portal *np = get_desc (fd); + int nwritten, maxbytes, n; + char *text, *ip = (char *)buf; + char obuf[SZ_OBUF]; + SIGFUNC sigpipe; + + + /* Enable a signal mask to catch SIGPIPE when the server has died. + */ + sigpipe = (SIGFUNC) signal (SIGPIPE, (SIGFUNC)nd_onsig); + recursion = 0; + + maxbytes = (np->domain == FIFO || (np->flags & F_TEXT)) ? SZ_OBUF : 0; + for (nwritten=0; nwritten < *nbytes; nwritten += n, ip+=n) { + n = *nbytes - nwritten; + if (maxbytes) + n = min (maxbytes, n); + + if (np->flags & F_TEXT) { + register XCHAR *ipp = (XCHAR *)ip; + register char *op = (char *)obuf; + register int nbytes = n / sizeof(XCHAR); + + while (--nbytes >= 0) + *op++ = *ipp++; + text = obuf; + + jmpset++; + if (setjmp (jmpbuf) == 0) { + if ((n = write(np->dataout, text, n / sizeof(XCHAR))) < 0) { + nwritten = ERR; + break; + } + } else { + nwritten = ERR; + break; + } + + n *= sizeof(XCHAR); + + } else { + text = ip; + if ((n = write (np->dataout, text, n)) < 0) { + nwritten = ERR; + break; + } + } + } + + /* Restore the signal mask. */ + jmpset = 0; + signal (SIGPIPE, sigpipe); + + kfp->nbytes = nwritten; + + return (nwritten); +} + + +/* ND_ONSIG -- Catch a signal. + * */ +static void +nd_onsig ( + int sig, /* signal which was trapped */ + int *arg1, /* not used */ + int *arg2 /* not used */ +) +{ + /* If we get a SIGPIPE writing to a server the server has probably + * died. Make it look like there was an i/o error on the channel. + */ + if (sig == SIGPIPE && recursion++ == 0) + ; + + if (jmpset) + longjmp (jmpbuf, sig); +} + + +/* ZAWTND -- "Wait" for an "asynchronous" read or write to complete, and + * return the number of bytes read or written, or ERR. + */ +int +ZAWTND (XINT *fd, XINT *status) +{ + if ((*status = zfd[*fd].nbytes) == ERR) + *status = XERR; + + return (*status); +} + + +/* ZSTTND -- Return file status information for a network device. + */ +int +ZSTTND (XINT *fd, XINT *param, XLONG *lvalue) +{ + switch (*param) { + case FSTT_BLKSIZE: + (*lvalue) = 0L; + break; + + case FSTT_FILSIZE: + (*lvalue) = 0L; + break; + + case FSTT_OPTBUFSIZE: + /* On some systems this parameter may be device dependent in which + * case device dependent code should be substituted here. + */ + (*lvalue) = ND_OPTBUFSIZE; + break; + + case FSTT_MAXBUFSIZE: + /* On some systems this parameter may be device dependent in which + * case device dependent code should be substituted here. + */ + (*lvalue) = ND_MAXBUFSIZE; + break; + + default: + (*lvalue) = XERR; + break; + } + + return (XOK); +} + + +/* + * Internal routines. + * ---------------------------- + */ + +/* GETSTR -- Internal routine to extract a metacharacter delimited substring + * from a formatted string. The metacharacter to be taken as the delimiter + * is passed as an argument. Any embedded whitespace between the tokens is + * stripped. The number of characters in the output token is returned as + * the function value, or zero if EOS or the delimiter is reached. + */ +static int +getstr (char **ipp, char *obuf, int maxch, int delim) +{ + register char *op, *ip = *ipp; + register char *otop = obuf + maxch; + + while (*ip && isspace(*ip)) + ip++; + for (op=obuf; *ip; ip++) { + if (*ip == delim) { + ip++; + break; + } else if (op < otop && !isspace(*ip)) + *op++ = *ip; + } + + *op = '\0'; + *ipp = ip; + + return (op - obuf); +} diff --git a/unix/os/zfiopl.c b/unix/os/zfiopl.c new file mode 100644 index 00000000..26468834 --- /dev/null +++ b/unix/os/zfiopl.c @@ -0,0 +1,279 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_prtype +#define import_spp +#include + +/* + * ZFIOPL -- IRAF FIO interface to plotter devices. A plotter + * is opened as a streaming type (no seeks) write-only binary device. + * FIO writes NCAR metacode to the plotter. This metacode is spooled + * in a temporary file and then disposed of to the plotter by calling + * an OS metacode translator to process the file. The spoolfile is then + * deleted. + * + * The system and device dependent information necessary to perform these + * functions is contained in three strings passed as the "device" parameter + * to ZOPNPL. The strings come from the GRAPHCAP entry for the device. + * The format of such a string is + * + * device D spoolfile D dispose_cmd EOS + * + * where DEVICE is the logical device name (not used herein), D is the field + * delimiter character (the first nonalphnumeric character encountered after + * the device field), SPOOLFILE is a UNIX pathname to be passed to MKTEMP + * to create the spoolfile pathname, and DISPOSE_CMD is a fill-in-the-blanks + * template for a UNIX shell command which will dispose of the spoolfile to + * the plotter device. + */ + +extern int save_prtype; + +#define SZ_OSCMD 512 /* buffer for dispose cmd */ +#define SZ_PLSTR 256 /* zopnpl plotter argument */ + +struct dplotter { + char *name; /* logical gdevice name */ + char *spoolfile; /* spoolfile string */ + char *dispose; /* dispose format string */ +}; + +struct oplotter { + long wbytes; /* nbytes written to device */ + struct dplotter *pl; /* device code as above */ + int status; /* status of last write */ + char spoolfile[SZ_PATHNAME+1]; +}; + +struct dplotter dpltr; /* machdep plotter info */ +struct oplotter pltr; /* open plotter descriptor */ +char plstr[SZ_PLSTR+1]; /* save zopnpl argument */ +int pltr_inuse = NO; /* set if plotter is open */ + + +extern int ZOPNBF (), ZCLSBF (), ZOSCMD (), ZFDELE (), ZARDBF (); +extern int ZAWRBF (), ZAWTBF (), ZSTTBF (); + + + +/* ZOPNPL -- Open a plotter device for binary file i/o. If we can talk + * directly to the plotter, do so, otherwise open a spoolfile which is + * to be sent to the plotter when ZCLSPL is later called. + */ +int +ZOPNPL ( + PKCHAR *plotter, /* plotter device descriptor */ + XINT *mode, /* file access mode */ + XINT *chan /* UNIX file number (output) */ +) +{ + register char *ip; + static char delim; + int fd; + + /* We do not see a need to have more than one plotter open at + * a time, and it makes things simpler. We can easily generalize + * to multiple open plotter devices in the future if justified. + */ + if (pltr_inuse == YES) { + *chan = XERR; + return (XERR); + } else + pltr_inuse = YES; + + /* Parse the plotter string into the name, spoolfile, and dispose + * strings. + */ + strncpy (plstr, (char *)plotter, SZ_PLSTR); + + /* Locate NAME field. */ + dpltr.name = plstr; + for (ip=plstr; isalnum(*ip); ip++) + ; + delim = *ip; + *ip++ = EOS; + + /* Locate SPOOLFILE field. */ + for (dpltr.spoolfile=ip; *ip && *ip != delim; ip++) + ; + *ip++ = EOS; + + /* Locate DISPOSE field. */ + for (dpltr.dispose=ip; *ip && *ip != delim; ip++) + ; + *ip++ = EOS; + + /* Initialize the open plotter descriptor. + */ + pltr.wbytes = 0L; + pltr.pl = &dpltr; + strcpy (pltr.spoolfile, dpltr.spoolfile); + if (dpltr.dispose[0] != EOS) + if ((fd = mkstemp (pltr.spoolfile)) >= 0) { + fchmod (fd, 0644); + close (fd); + } + + return ZOPNBF ((PKCHAR *)pltr.spoolfile, mode, chan); +} + + +/* ZCLSPL -- To close a plotter we merely close the "spoolfile", and then + * dispose of the spoolfile to the OS if so indicated. + */ +int +ZCLSPL (XINT *chan, XINT *status) +{ + static PKCHAR xnullstr[1] = { EOS }; + register char *ip, *op, *f; + PKCHAR cmd[(SZ_OSCMD+1) / sizeof(PKCHAR)]; + XINT junk; + + ZCLSBF (chan, status); + pltr_inuse = NO; + + /* Dispose of the output file if so indicated. Do not bother to + * check the status return, since we cannot return status to FIO + * from here anyhow. Do not dispose of the file if it is empty. + * If the file is disposed of by the OS, we assume that it is also + * deleted after printing. If file is not disposed to the OS, we + * delete it ourselves. + */ + if (*(pltr.pl->dispose) != EOS) { + if (pltr.wbytes > 0) { + PKCHAR out[SZ_FNAME+1]; + + /* Build up command line by substituting the spoolfile name + * everywhere the macro "$F" appears in the "dispose" text. + */ + op = (char *)cmd; + for (ip=pltr.pl->dispose; (*op = *ip++) != EOS; op++) + if (*op == '$' && *ip == 'F') { + for (f=pltr.spoolfile; (*op = *f++) != EOS; op++) + ; + /* Overwrite EOS, skip over 'F' */ + --op, ip++; + } + strcpy ((char *)out, + save_prtype == PR_CONNECTED ? "/dev/tty" : ""); + ZOSCMD (cmd, xnullstr, out, out, &junk); + } else + ZFDELE ((PKCHAR *)pltr.spoolfile, &junk); + } + + return (*status); +} + + +/* ZARDPL -- For UNIX, the read and write routines are just the binary file + * i/o routines. Note that packing of chars into bytes, mapping of escape + * sequences, etc. is done by the high level code; our function is merely to + * move the data to the device. The read primitive is not likely to be needed + * for a plotter, but you never know... + */ +int +ZARDPL ( + XINT *chan, + XCHAR *buf, + XINT *maxbytes, + XLONG *offset +) +{ + return ZARDBF (chan, buf, maxbytes, offset); +} + + +/* ZAWRPL -- Write a metafile record to the plotter spoolfile. We are always + * called to write metacode; zfiopl is not used to send device codes to the + * plotter. Our job is to make the NSPP metacode record passed on to us by + * WRITEB look like whatever the system metacode translators expect. On the + * KPNO system, the metacode translators are Fortran programs, expecting an + * unformatted binary metacode file as input. We simulate this file by + * adding the integer byte count of the record to the beginning and end of + * each record. + * + * N.B.: We ASSUME that the FIO buffer has been set to the size of a metafile + * record, i.e., 1440 bytes or 720 chars on the VAX. + */ +int +ZAWRPL ( + XINT *chan, + XCHAR *buf, + XINT *nbytes, + XLONG *offset /* not used */ +) +{ + static XINT hdrlen=sizeof(int); + static XLONG noffset=0L; + XINT status; + int reclen; + + /* Write out the integer record header. Set the file offset to zero + * since the file is sequential, and the offsets do not include the + * record headers anyhow so are wrong. + */ + reclen = *nbytes; + ZAWRBF (chan, (XCHAR *)&reclen, &hdrlen, &noffset); + ZAWTBF (chan, &status); + + /* Write the metacode data. + */ + pltr.wbytes += *nbytes; + ZAWRBF (chan, buf, nbytes, &noffset); + ZAWTBF (chan, &pltr.status); + + /* Write out the integer record trailer. Set the file offset to zero + * since the file is sequential, and the offsets do not include the + * record headers anyhow so are wrong. + */ + reclen = *nbytes; + ZAWRBF (chan, (XCHAR *)&reclen, &hdrlen, &noffset); + ZAWTBF (chan, &status); + + return (status); +} + + +/* ZAWTPL -- Return the status of the write (we do not read metafiles with + * the plotter interface). The status byte count does not include the + * record header, since that was written with a separate write unbeknownst + * to FIO, so the status value returned refers only to the metacode data. + */ +int +ZAWTPL (XINT *chan, XINT *status) +{ + ZAWTBF (chan, status); + if (*status > 0) + *status = pltr.status; + + return (*status); +} + + +/* ZSTTPL -- Get status for the plotter output file. Plotter output is + * strictly sequential due to the way metacode records are packaged in + * ZAWRPL. Hence we must always return blksize=0 to indicate that the + * device is a streaming file, regardless of whether or not the output + * is spooled. + */ +int +ZSTTPL (XINT *chan, XINT *param, XLONG *lvalue) +{ + switch (*param) { + case FSTT_BLKSIZE: + *lvalue = 0L; + break; + default: + return ZSTTBF (chan, param, lvalue); + } + return (XOK); +} diff --git a/unix/os/zfiopr.c b/unix/os/zfiopr.c new file mode 100644 index 00000000..9bf2dfed --- /dev/null +++ b/unix/os/zfiopr.c @@ -0,0 +1,499 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_prtype +#define import_zfstat +#define import_spp +#include + +extern int errno; /* error code returned by the kernel */ +#ifdef SYSV +#define vfork fork +#else +# ifdef sun +# include +# endif +#endif + +extern void pr_enter (int pid, int inchan, int outchan); + + + +/* ZFIOPR -- File i/o to a subprocess. A "connected" subprocess is connected + * to the parent via two IPC channels (read and write), and is analogous to a + * streaming binary file: + * + * zopcpr open process + * zclcpr close process + * zardpr read from process + * zawrpr write to process + * zawtpr wait for i/o + * zsttpr get channel/device status + * also + * zintpr interrupt process + * + * See also the zopdpr, zcldpr primitives, used to spawn "detached" processes + * which do not communicate with the parent. + */ + +#define IPC_MAGIC 01120 /* First 2 bytes of IPC block */ +#define SZ_TTYIBUF 512 /* buffer size if reading TTY */ +#define SZ_TTYOBUF 2048 /* buffer size if writing TTY */ +#define MAX_TRYS 2 /* max interupted reads */ + +#define mask(s) (1 << ((s) - 1)) + +int pr_ionbytes[MAXOFILES]; /* nbytes read|written on channel */ +int debug_ipc = 0; /* print debug info on stderr */ +int ipc_in = 0; /* logfile for IPC input */ +int ipc_out = 0; /* logfile for IPC output */ +int ipc_isatty = 0; /* set when debugging IPC at TTY */ + + +/* ZOPCPR -- Open a connected subprocess. Spawn process and open bidirectional + * IPC channels, implemented with pipes for this version of Berkeley UNIX. + */ +int +ZOPCPR ( + PKCHAR *osfn, /* name of executable file */ + XINT *inchan, + XINT *outchan, /* IPC channels (parent reads inchan) */ + XINT *pid +) +{ + int pin[2], pout[2]; + int maxforks = 3, fd; + + if (debug_ipc) + fprintf (stderr, "zopcpr (`%s')", (char *)osfn); + + + /* Check that the process file exists and is executable. + */ + if (access ((char *)osfn, 1) == ERR) { + *pid = XERR; + return (XERR); + } + + /* Open binary IPC channels. Clear byte counts. + */ + pin[0] = pin[1] = -1; + pout[0] = pout[1] = -1; + + if (pipe(pin) == ERR || pipe(pout) == ERR) + goto err; + else if (pin[0] >= MAXOFILES || pin[1] >= MAXOFILES || + pout[0] >= MAXOFILES || pout[1] >= MAXOFILES) { +err: close (pin[0]); close (pin[1]); + close (pout[0]); close (pout[1]); + *pid = XERR; + return (XERR); + } + + pr_ionbytes[pin[0]] = 0; + pr_ionbytes[pin[1]] = 0; + pr_ionbytes[pout[0]] = 0; + pr_ionbytes[pout[1]] = 0; + + /* Create child process. Vfork is used to avoid necessity to copy + * the full address space of the parent, since we are going to overlay + * a new process immediately with Execl anyhow. The child inherits + * the open stdio files. The fork can fail if swap space is full or + * if we have too many processes. + */ + while ((*pid = vfork()) == ERR) { + if (--maxforks == 0) { + close (pin[0]); close (pin[1]); + close (pout[0]); close (pout[1]); + *pid = XERR; + return (XERR); + } + sleep (2); + } + + if (*pid == 0) { + /* New, child process. Make child think the pipe is its stdin/out. + */ + struct rlimit rlim; + int maxfd; + + if (getrlimit (RLIMIT_NOFILE, &rlim)) + maxfd = MAXOFILES; + else + maxfd = rlim.rlim_cur; + + close (pin[0]); + close (pout[1]); + close (0); dup (pout[0]); close (pout[0]); + close (1); dup (pin[1]); close (pin[1]); + + /* Disable SIGINT so that child process does not die when the + * parent process is interrupted. Parent sends SIGTERM to + * interrupt a child process. + */ + signal (SIGINT, SIG_IGN); + + /* Arrange for the local file descriptors of the parent to be + * closed in the child if the exec succeeds. IRAF subprocesses + * do not expect to inherit any file descriptors other than + * stdin, stdout, and stderr. + */ + for (fd=3; fd < maxfd; fd++) + fcntl (fd, F_SETFD, 1); + + /* Exec the new process. Will not return if successful. + * The "-c" flag tells the subprocess that it is a connected + * subprocess. + */ + execl ((char *)osfn, (char *)osfn, "-c", (char *) 0); + + /* If we get here the new process could not be executed for some + * reason. Shutdown, calling _exit to avoid flushing parent's + * io buffers. Parent will receive the X_IPC exception when it + * subsequently tries to write to the child. + */ + _exit (1); + + } else { + + /* Existing, parent process. */ + close (pin[1]); + close (pout[0]); + *inchan = pin[0]; + *outchan = pout[1]; + + /* Save pid in parent's process table. Entry cleared when + * pr_wait is called to wait for process to terminate. Also save + * channel numbers in process table since only the pid is passed + * when the process is closed. + */ + pr_enter (*pid, pin[0], pout[1]); + + if (debug_ipc) + fprintf (stderr, " [%ld]\n", (long) *pid); + } + + return (XOK); +} + + +/* ZCLCPR -- Close a connected subprocess. Wait for subprocess to terminate, + * close the IPC channels, and return the exit status. + */ +int +ZCLCPR (XINT *pid, XINT *exit_status) +{ + int inchan, outchan; + extern int pr_getipc(), pr_wait(); + + + if (pr_getipc ((int)*pid, &inchan, &outchan) == ERR) + *exit_status = XERR; + else { + close (outchan); + close (inchan); + *exit_status = pr_wait ((int)*pid); + } + + if (debug_ipc) + fprintf (stderr, "[%ld] terminated, exit code %ld\n", + (long)*pid, (long)*exit_status); + + return (*exit_status); +} + + +/* ZARDPR -- Read next record from an IPC channel. Since UNIX pipes are byte + * streams we must take special measures to transmit data through a pipe in + * records. Each block of data is preceded by a 4-byte header consisting + * of a 2-byte magic number (used to verify that the correct protocol is in + * use on the channel) and a 2-byte count of the number of bytes in the block. + * To read a block we must read the count and then issue successive read + * requests until the entire block has been read. The byte count (excluding the + * 4-byte header) is saved in a static table for return to the high level code + * in a subsequent call to ZAWTPR. Disaster occurs if the actual block length + * does not agree with the header, but that cannot happen since only ZAWRPR + * writes to an IPC channel. + */ +int +ZARDPR ( + XINT *chan, + XCHAR *buf, + XINT *maxbytes, + XLONG *loffset /* not used */ +) +{ + register char *op; + register int fd, nbytes; + int record_length, status; + short temp; +#ifdef POSIX + sigset_t sigmask_save, set; +#else + int sigmask_save; +#endif + + fd = *chan; + op = (char *)buf; + + if (debug_ipc) + fprintf (stderr, + "[%d] initiate read for %ld bytes from IPC channel %d\n", + getpid(), (long)*maxbytes, fd); + + /* In TTY debug mode we simulate IPC i/o but are actually talking to + * a terminal. Omit the packet headers and unpack input into XCHAR. + * If a interrupt ocurrs while the read is pending and control returns + * to the do-while, try to complete the read successfully. + */ + if (ipc_isatty) { + char ibuf[SZ_TTYIBUF], *ip; + XCHAR *xop; + int maxch = min (SZ_TTYIBUF, *maxbytes / sizeof(XCHAR)); + int ntrys = MAX_TRYS; + + do { + errno = 0; + if ((pr_ionbytes[fd] = nbytes = read (fd, ibuf, maxch)) > 0) { + for (ip=ibuf, xop=buf; --nbytes >= 0; ) + *xop++ = *ip++; + pr_ionbytes[fd] *= sizeof (XCHAR); + } + } while (nbytes <= 0 && errno == EINTR && --ntrys >= 0); + + return (XOK); + } + + /* Read 2-byte magic number to verify that the channel was written to + * by ZAWRPR and that we are at the start of a record. + */ + switch (status = read (fd, &temp, 2)) { + case 0: + pr_ionbytes[fd] = 0; + return (0); + case ERR: + pr_ionbytes[fd] = ERR; + return (XERR); + default: + if (status != 2 || temp != IPC_MAGIC) { + pr_ionbytes[fd] = ERR; + return (XERR); + } + } + + if (ipc_in > 0) + write (ipc_in, (char *)&temp, 2); + + /* Get byte count of record. + */ + if (read (fd, &temp, 2) != 2) { + pr_ionbytes[fd] = ERR; + return (XERR); + } + record_length = temp; + nbytes = min (record_length, *maxbytes); + pr_ionbytes[fd] = nbytes; + if (ipc_in > 0) + write (ipc_in, (char *)&temp, 2); + + /* Now read exactly nbytes of data from channel into user buffer. + * Return actual byte count if EOF is seen. If ERR is seen return + * ERR. If necessary multiple read requests are issued to read the + * entire record. This is implemented as a critical section to + * prevent corruption of the IPC protocol when an interrupt occurs. + */ +#ifdef POSIX + sigemptyset (&set); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGTERM); + sigprocmask (SIG_BLOCK, &set, &sigmask_save); +#else + sigmask_save = sigblock (mask(SIGINT) | mask(SIGTERM)); +#endif + + while (nbytes > 0) { + switch (status = read (fd, op, nbytes)) { + case 0: + pr_ionbytes[fd] -= nbytes; + goto reenab_; + case ERR: + pr_ionbytes[fd] = ERR; + goto reenab_; + default: + nbytes -= status; + op += status; + } + } + + if (debug_ipc) { +/* +char ch, *bp = buf, nc=0; +for (nc=0; nc < 30; nc++) { + ch = (char)(*(buf + nc)); + fprintf (stderr, "rd ipc_in=%d [%d] '%c' %d \n", ipc_in, nc, ch, ch); +} +*/ + fprintf (stderr, "[%d] read %ld bytes from IPC channel %d:\n", + getpid(), (long) (op - (char *)buf), fd); + write (2, (char *)buf, op - (char *)buf); + } + + if (ipc_in > 0) + write (ipc_in, (char *)buf, op - (char *)buf); + + /* If the record is larger than maxbytes, we must read and discard + * the additional bytes. The method used is inefficient but it is + * unlikely that we will be called to read less than a full record. + */ + for (nbytes = *maxbytes; nbytes < record_length; nbytes++) + if (read (fd, &temp, 1) <= 0) + break; +reenab_: +#ifdef POSIX + sigprocmask (SIG_SETMASK, &sigmask_save, NULL); +#else + sigsetmask (sigmask_save); +#endif + + return (XOK); +} + + +/* ZAWRPR -- Write to an IPC channel. Write the IPC block header followed by + * the data block. + */ +int +ZAWRPR ( + XINT *chan, + XCHAR *buf, + XINT *nbytes, + XLONG *loffset +) +{ + register int fd; + short temp; +#ifdef POSIX + sigset_t sigmask_save, set; +#else + int sigmask_save; +#endif + + fd = *chan; + + /* In TTY debug mode we simulate IPC i/o but are actually talking to + * a terminal. Omit the packet headers and pack XCHAR output into + * bytes. + */ + if (ipc_isatty) { + char obuf[SZ_TTYOBUF], *op; + XCHAR *ip; + int nchars, n; + + n = nchars = min (SZ_TTYOBUF, *nbytes / sizeof(XCHAR)); + for (ip=buf, op=obuf; --n >= 0; ) + *op++ = *ip++; + if ((pr_ionbytes[fd] = write (fd, obuf, nchars)) > 0) + pr_ionbytes[fd] *= sizeof (XCHAR); + return (XOK); + } + + /* Write IPC block header. + */ +#ifdef POSIX + sigemptyset (&set); + sigaddset (&set, SIGINT); + sigaddset (&set, SIGTERM); + sigprocmask (SIG_BLOCK, &set, &sigmask_save); +#else + sigmask_save = sigblock (mask(SIGINT) | mask(SIGTERM)); +#endif + + temp = IPC_MAGIC; + write (fd, &temp, 2); + if (ipc_out > 0) + write (ipc_out, &temp, 2); + temp = *nbytes; + write (fd, &temp, 2); + if (ipc_out > 0) + write (ipc_out, &temp, 2); + + /* Write data block. + */ + pr_ionbytes[fd] = write (fd, (char *)buf, (int)*nbytes); + if (ipc_out > 0) + write (ipc_out, (char *)buf, (int)*nbytes); + +#ifdef POSIX + sigprocmask (SIG_SETMASK, &sigmask_save, NULL); +#else + sigsetmask (sigmask_save); +#endif + + if (debug_ipc) { +/* +char ch, *bp = buf, nc=0; +for (nc=0; nc < 30; nc++) { + ch = (char)(*(buf + nc)); + fprintf (stderr, "wr ipc_out=%d [%d] '%c' %d pr_io=%d\n", + ipc_out, nc, ch, ch, pr_ionbytes[fd]); +} +*/ + fprintf (stderr, "[%d] wrote %d bytes to IPC channel %d:\n", + getpid(), (int)*nbytes, fd); + write (2, (char *)buf, (int)*nbytes); + } + + return (XOK); +} + + +/* ZAWTPR -- Wait for i/o to an IPC channel. Since UNIX pipes are not + * asynchronous we do not really wait, rather we return the status value + * (byte count) from the last read or write to the channel. + */ +int +ZAWTPR (XINT *chan, XINT *status) +{ + if ((*status = pr_ionbytes[*chan]) == ERR) + *status = XERR; + + return (*status); +} + + +/* ZSTTPR -- Get binary file status for an IPC channel. An IPC channel is a + * streaming binary file. + */ +int +ZSTTPR ( + XINT *chan, /* not used; all IPC channels have same status */ + XINT *param, + XLONG *lvalue +) +{ + switch (*param) { + case FSTT_BLKSIZE: + case FSTT_FILSIZE: + *lvalue = 0; + break; + case FSTT_OPTBUFSIZE: + *lvalue = PR_OPTBUFSIZE; + break; + case FSTT_MAXBUFSIZE: + *lvalue = PR_MAXBUFSIZE; + break; + default: + *lvalue = XERR; + } + + return (*lvalue); +} diff --git a/unix/os/zfiosf.c b/unix/os/zfiosf.c new file mode 100644 index 00000000..a6efe7ed --- /dev/null +++ b/unix/os/zfiosf.c @@ -0,0 +1,126 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + +/* + * ZFIOSF -- Static file device driver. In the 4.1BSD UNIX kernel the ordinary + * binary file driver is used for static files (files that do not change in + * size once created by ZFALOC), hence all we need do to implement a SF routine + * is call the corresponding BF routine. A great gain in i/o efficiency could + * probably be gained by replacing this driver by one using raw i/o, but we do + * not want to bother with that for 4.1BSD UNIX because the time would be + * better spent doing it for 4.2BSD. + * + * If anyone is stuck with 4.1BSD for some reason and wants fast static file + * i/o, the strategy is to try to allocate contiguous files with ZFALOC, either + * in a raw partition using a special file manager or within an ordinary + * partition adding a new system call to allocate contiguous storage for a file. + * The latter scheme has the advantage that files thus created are ordinary + * UNIX files and can be accessed normally as well as by the static file driver. + * Given a contiguous or near-contiguous file on disk, all the static file + * driver needs for direct access with large transfers is the physical block + * offset of the file. The raw device is then accessed via physio calls to + * transfer data directly to or from the user's buffer, bypassing the system + * buffer cache. Write perm is required on the raw device for the target + * filesystem; this opens up the possibility of trashing the files system. + * Static file access should be restricted to one or more large temporary files + * systems. If one gets really ambitious a special UNIX driver can be added to + * permit asynchronous i/o, bypassing the UNIX files system entirely except + * during file creation and deletion. + */ + +extern int ZOPNBF(), ZCLSBF (), ZARDBF (), ZAWRBF (), ZAWTBF (), ZSTTBF (); + + +/* ZOPNSF -- Open a static file. Only RO, WO, and RW modes are permitted + * for static files, since allocation is via ZFALOC and appending is not + * permitted. + */ +int +ZOPNSF ( + PKCHAR *osfn, /* UNIX name of file */ + XINT *mode, /* file access mode */ + XINT *chan /* file number (output) */ +) +{ + switch (*mode) { + case READ_ONLY: + case WRITE_ONLY: + case READ_WRITE: + return ZOPNBF (osfn, mode, chan); + break; + default: + *chan = XERR; + return (*chan); + } +} + + +/* ZCLSSF -- Close a static file. + */ +int +ZCLSSF (XINT *fd, XINT *status) +{ + return ZCLSBF (fd, status); +} + + +/* ZARDSF -- "Asynchronous" static block read. Initiate a read of at most + * maxbytes bytes from the file FD into the buffer BUF. Status is returned + * in a subsequent call to ZAWTSF. + */ +int +ZARDSF ( + XINT *chan, /* UNIX file number */ + XCHAR *buf, /* output buffer */ + XINT *maxbytes, /* max bytes to read */ + XLONG *offset /* 1-indexed file offset to read at */ +) +{ + return ZARDBF (chan, buf, maxbytes, offset); +} + + +/* ZAWRSF -- "Asynchronous" static block write. Initiate a write of exactly + * nbytes bytes from the buffer BUF to the file FD. Status is returned in a + * subsequent call to ZAWTSF. + */ +int +ZAWRSF ( + XINT *chan, /* UNIX file number */ + XCHAR *buf, /* buffer containing data */ + XINT *nbytes, /* nbytes to be written */ + XLONG *offset /* 1-indexed file offset */ +) +{ + return ZAWRBF (chan, buf, nbytes, offset); +} + + +/* ZAWTSF -- "Wait" for an "asynchronous" read or write to complete, and + * return the number of bytes read or written, or ERR. + */ +int +ZAWTSF (XINT *fd, XINT *status) +{ + return ZAWTBF (fd, status); +} + + +/* ZSTTSF -- Return status on a static file. + */ +int +ZSTTSF ( + XINT *fd, + XINT *param, + XLONG *lvalue +) +{ + return ZSTTBF (fd, param, lvalue); +} diff --git a/unix/os/zfiotx.c b/unix/os/zfiotx.c new file mode 100644 index 00000000..e387dfaa --- /dev/null +++ b/unix/os/zfiotx.c @@ -0,0 +1,991 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef LINUX +#define USE_SIGACTION +#endif + +#ifdef SYSV +#include +#else +#include +#endif + +#ifndef O_NDELAY +#include +#endif + +#define import_kernel +#define import_knames +#define import_zfstat +#define import_spp +#include + +/* + * ZFIOTX -- File i/o to textfiles, for UNIX 4.1BSD. This driver is used for + * both terminals and ordinary disk text files. I/O is via the C library + * stdio routines, which provide buffering. + * + * On input, we must check for newline and terminate when it has been read, + * including the newline character in the return buffer and in the character + * count. If the buffer limit is reached before newline is found, we return + * the line without a newline, and return the rest of the line in the next + * read request. If a single character is requested character mode is + * asserted, i.e., if the device is a terminal RAW is enabled and ECHO and + * CRMOD (CR to LF mapping) are disabled. A subsequent request to read or + * write more than one character restores line mode. + * + * On output, we transmit the specified number of characters, period. + * No checking for newline or EOS is performed. Text may contain null + * characters (note that EOS == NUL). There is no guarantee that the + * output line will contain a newline. When flushing partial lines to a + * terminal, this is often not the case. When FIO writes a very long + * line and the FIO buffer overflows, it will flush a partial line, + * passing the rest of the line (with newline delimiter) in the next + * write request. The newline should be included in the data as stored + * on disk, if possible. It is not necessary to map newlines upon output + * because UNIX does this already. + * + * N.B.: An SPP char is usually not a byte, so these routines usually + * perform type conversion. The actual SPP datatype is defined in language.h + * An SPP char is a signed integer, but only values in the range 0-127 may + * be written to a text file. We assume that character set conversion (e.g., + * ASCII to EBCDIC) is not necessary since all 4.1BSD hosts we are aware of + * are ASCII machines. + */ + +#define MAXOTTYS 20 /* max simultaneous open ttys */ +#define MAX_TRYS 2 /* max interrupted trys to read */ +#define NEWLINE '\n' + +/* The ttyport descriptor is used by the tty driver (zfiotx). */ +struct ttyport { + int inuse; /* port in use */ + int chan; /* host channel (file descrip.) */ + unsigned device; /* tty device number */ + int flags; /* port flags */ + char redraw; /* redraw char, sent after susp */ +#ifdef SYSV + struct termios tc; /* normal tty state */ + struct termios save_tc; /* saved rawmode tty state */ +#else + struct sgttyb tc; /* normal tty state */ + struct sgttyb save_tc; /* saved rawmode tty state */ +#endif +}; + + + +#define CTRLC 3 +extern int errno; +static jmp_buf jmpbuf; +static int tty_getraw = 0; /* raw getc in progress */ +#ifdef USE_SIGACTION +static struct sigaction sigint, sigterm; +static struct sigaction sigtstp, sigcont; +static struct sigaction oldact; +#else +static SIGFUNC sigint, sigterm; +static SIGFUNC sigtstp, sigcont; +#endif +static void tty_rawon(), tty_reset(), uio_bwrite(); +static void tty_onsig(), tty_stop(), tty_continue(); + +/* The ttyports array describes up to MAXOTTYS open terminal i/o ports. + * Very few processes will ever have more than one or two ports open at + * one time. ttyport descriptors are allocated one per tty device, using + * the minor device number to identify the device. This serves to + * uniquely identify each tty device regardless of the file descriptor + * used to access it. Multiple file descriptors (e.g. stdin, stdout, + * stderr) used to access a single device must use description of the + * device state. + */ +struct ttyport ttyports[MAXOTTYS]; +struct ttyport *lastdev = NULL; + +/* Omit this for now; it was put in for an old Linux libc bug, and since libc + * is completely different now the need for it has probably gone away. + * + * #ifdef LINUX + * #define FCANCEL + * #endif + */ +#ifdef FCANCEL +/* The following definition has intimate knowledge of the STDIO structures. */ +#define fcancel(fp) ( \ + (fp)->_IO_read_ptr = (fp)->_IO_read_end = (fp)->_IO_read_base,\ + (fp)->_IO_write_ptr = (fp)->_IO_write_end = (fp)->_IO_write_base) +#endif + + +/* ZOPNTX -- Open or create a text file. The pseudo-files "unix-stdin", + * "unix-stdout", and "unix-stderr" are treated specially. These denote the + * UNIX stdio streams of the process. These streams are not really opened + * by this driver, but ZOPNTX should be called on these streams to + * properly initialize the file descriptor. + */ +int +ZOPNTX ( + PKCHAR *osfn, /* UNIX filename */ + XINT *mode, /* file access mode */ + XINT *chan /* UNIX channel of file (output) */ +) +{ + register int fd; + register FILE *fp; + struct stat filestat; + int newmode, maskval; + FILE *fopen(); + char *fmode; + + /* Map FIO access mode into UNIX/stdio access mode. + */ + switch (*mode) { + case READ_ONLY: + fmode = "r"; + break; + case READ_WRITE: + fmode = "r+"; /* might not work on old systems */ + break; + case APPEND: + fmode = "a"; + break; + case WRITE_ONLY: + fmode = "w"; + break; + case NEW_FILE: /* create for appending */ + fmode = "w"; + break; + default: + goto error; + } + + /* Open or create file. If a new file is created it is necessary + * to explicitly set the file access permissions as indicated by + * FILE_MODEBITS in kernel.h. This is done in such a way that the + * user's UMASK bits are preserved. + */ + if (strcmp ((char *)osfn, U_STDIN) == 0) { + fp = stdin; + } else if (strcmp ((char *)osfn, U_STDOUT) == 0) { + fp = stdout; + } else if (strcmp ((char *)osfn, U_STDERR) == 0) { + fp = stderr; + } else if ((fp = fopen ((char *)osfn, fmode)) == NULL) + goto error; + + fd = fileno (fp); + if (fstat (fd, &filestat) == ERR) { + if (fd > 2) + fclose (fp); + goto error; + } else if (fd >= MAXOFILES) { + if (fd > 2) + fclose (fp); + goto error; + } + + /* If regular file apply the umask. */ + if (fd > 2 && S_ISREG(filestat.st_mode) && + (*mode == WRITE_ONLY || *mode == NEW_FILE)) { + umask (maskval = umask (022)); + newmode = ((filestat.st_mode | 066) & ~maskval); + (void) chmod ((char *)osfn, newmode); + } + + /* Set up kernel file descriptor. + */ + zfd[fd].fp = fp; + zfd[fd].fpos = 0; + zfd[fd].nbytes = 0; + zfd[fd].io_flags = 0; + zfd[fd].port = (char *) NULL; + + zfd[fd].flags = (KF_NOSEEK | KF_NOSTTY); + if (S_ISREG (filestat.st_mode)) + zfd[fd].flags &= ~KF_NOSEEK; + if (S_ISCHR (filestat.st_mode)) + zfd[fd].flags &= ~KF_NOSTTY; + + /* If we are opening a terminal device set up ttyport descriptor. */ + if (!(zfd[fd].flags & KF_NOSTTY)) { + register struct ttyport *port; + register unsigned device; + register int i; + + /* Check if we already have a descriptor for this device. */ + device = (filestat.st_dev << 16) + filestat.st_rdev; + for (i=0, port = &ttyports[0]; i < MAXOTTYS; i++, port++) + if (port->inuse && port->device == device) { + zfd[fd].port = (char *) port; + port->inuse++; + goto done; + } + + /* Fill in a fresh descriptor. */ + port = &ttyports[0]; + for (i=MAXOTTYS; --i >= 0 && port->inuse; port++) + ; + if (i >= MAXOTTYS) + goto error; + + port->chan = fd; + port->device = device; + port->flags = 0; + port->redraw = 0; + port->inuse = 1; + + zfd[fd].port = (char *) port; + } + +done: + *chan = fd; + return (*chan); + +error: + *chan = XERR; + return (*chan); +} + + +/* ZCLSTX -- Close a text file. + */ +int +ZCLSTX (XINT *fd, XINT *status) +{ + register struct fiodes *kfp = &zfd[*fd]; + register struct ttyport *port = (struct ttyport *) kfp->port; + + /* Disable character mode if still in effect. If this is necessary + * then we have already saved the old tty status flags in sg_flags. + */ + if (port && (port->flags & KF_CHARMODE)) + tty_reset (port); + + /* Close the file. Set file pointer field of kernel file descriptor + * to null to indicate that the file is closed. + * + * [NOTE] -- fclose errors are ignored if we are closing a terminal + * device. This was necessary on the Suns and it was not clear why + * a close error was occuring (errno was EPERM - not owner). + */ + *status = (fclose(kfp->fp) == EOF && kfp->flags&KF_NOSTTY) ? XERR : XOK; + + kfp->fp = NULL; + if (port) { + kfp->port = NULL; + if (--port->inuse < 0) + port->inuse = 0; + if (lastdev == port) + lastdev = NULL; + } + + return (*status); +} + + +/* ZFLSTX -- Flush any buffered textual output. + */ +int +ZFLSTX (XINT *fd, XINT *status) +{ + *status = (fflush (zfd[*fd].fp) == EOF) ? XERR : XOK; + return ((int) *status); +} + + +/* ZGETTX -- Get a line of text from a text file. Unpack machine chars + * into type XCHAR. If output buffer is filled before newline is encountered, + * the remainder of the line will be returned in the next call, and the + * current line will NOT be newline terminated. If maxchar==1 assert + * character mode, otherwise assert line mode. + */ +int +ZGETTX (XINT *fd, XCHAR *buf, XINT *maxchars, XINT *status) +{ + register FILE *fp; + register XCHAR *op; + register int ch, maxch = *maxchars; + register struct fiodes *kfp; + struct ttyport *port; + int nbytes, ntrys; + + if (maxch <= 0) { + *status = 0; + return (*status); + } + + kfp = &zfd[*fd]; + port = (struct ttyport *) kfp->port; + fp = kfp->fp; + + /* If maxch=1 assert char mode if legal on device, otherwise clear + * char mode if set. Ignore ioctl errors if ioctl is illegal on + * device. + */ + if (port) { + if (maxch == 1 && !(port->flags & KF_CHARMODE)) + tty_rawon (port, 0); + else if (maxch > 1 && (port->flags & KF_CHARMODE)) { + /* Disable character mode. If this is necessary then we have + * already saved the old tty status flags in sg_flags. + */ + tty_reset (port); + } + } + + /* Copy the next line of text into the user buffer. Keep track of + * file offsets of current line and next line for ZNOTTX. A call to + * ZNOTTX will return the value of kfp->fpos, the file offset of the + * next line to be read (unless newline has not yet been seen on the + * current line due to a partial read). The following while loop is + * the inner loop of text file input, hence is heavily optimized at + * the expense of some clarity. If the host is non-ASCII add a lookup + * table reference to this loop to map chars from the host character + * set to ASCII. + * + * N.B.: If an interrupt occurs during the read and the output buffer + * is still empty, try to complete the read. This can happen when + * an interrupt occurs while waiting for input from the terminal, in + * which case recovery is probably safe. + */ + if (!port || !(port->flags & KF_CHARMODE)) { + /* Read in line mode. + */ + ntrys = MAX_TRYS; + do { + clearerr (fp); + op = buf; + errno = 0; + while (*op++ = ch = getc(fp), ch != EOF) { + if (--maxch <= 0 || ch == NEWLINE) + break; + } +#ifdef FCANCEL + if (errno == EINTR) + fcancel (fp); +#endif + } while (errno == EINTR && op-1 == buf && --ntrys >= 0); + + *op = XEOS; + nbytes = *maxchars - maxch; + + } else if (kfp->flags & KF_NDELAY) { + /* Read a single character in nonblocking raw mode. Zero + * bytes are returned if there is no data to be read. + */ + struct timeval timeout; + int chan = fileno(fp); + fd_set rfds; + char data[1]; + + FD_ZERO (&rfds); + FD_SET (chan, &rfds); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + clearerr (fp); + + if (select (chan+1, &rfds, NULL, NULL, &timeout)) { + if (read (chan, data, 1) != 1) { + *status = XERR; + return (XERR); + } + ch = *data; + goto outch; + } else { + *buf = XEOS; + *status = 0; + return (*status); + } + + } else { + /* Read a single character in raw mode. Catch interrupts and + * return the interrupt character as an ordinary character. + * We map interrupts in this way, rather than use raw mode + * (which disables interrupts and all other input processing) + * because ctrl/s ctrl/q is disabled in raw mode, and that can + * cause sporadic protocol failures. + */ +#ifdef USE_SIGACTION + sigint.sa_handler = (SIGFUNC) tty_onsig; + sigemptyset (&sigint.sa_mask); + sigint.sa_flags = SA_NODEFER; + sigaction (SIGINT, &sigint, &oldact); + + sigterm.sa_handler = (SIGFUNC) tty_onsig; + sigemptyset (&sigterm.sa_mask); + sigterm.sa_flags = SA_NODEFER; + sigaction (SIGTERM, &sigterm, &oldact); +#else + sigint = (SIGFUNC) signal (SIGINT, (SIGFUNC)tty_onsig); + sigterm = (SIGFUNC) signal (SIGTERM, (SIGFUNC)tty_onsig); +#endif + tty_getraw = 1; + + /* Async mode can be cleared by other processes (e.g. wall), + * so reset it on every nonblocking read. This code should + * never be executed as KF_NDELAY is handled specially above, + * but it does no harm to leave it in here anyway. + */ + if (kfp->flags & KF_NDELAY) + fcntl (*fd, F_SETFL, kfp->io_flags | O_NDELAY); + + if ((ch = setjmp (jmpbuf)) == 0) { + clearerr (fp); + ch = getc (fp); + } +#ifdef FCANCEL + if (ch == CTRLC) + fcancel (fp); +#endif + +#ifdef USE_SIGACTION + sigaction (SIGINT, &oldact, NULL); + sigaction (SIGTERM, &oldact, NULL); +#else + signal (SIGINT, sigint); + signal (SIGTERM, sigterm); +#endif + tty_getraw = 0; + + /* Clear parity bit just in case raw mode is used. + */ +outch: op = buf; + if (ch == EOF) { + *op++ = ch; + nbytes = 0; + } else { + *op++ = (ch & 0177); + nbytes = 1; + } + *op = XEOS; + } + + /* Check for errors and update offsets. If the last char copied + * was EOF step on it with an EOS. In nonblocking raw mode EOF is + * indistinguishable from a no-data read, but it doesn't matter + * especially since a ctrl/d, ctrl/z, etc. typed by the user is + * passed through as data. + */ + if (ferror (fp)) { + clearerr (fp); + *op = XEOS; + nbytes = (kfp->flags&KF_NDELAY && errno==EWOULDBLOCK) ? 0 : XERR; + } else { + kfp->nbytes += nbytes; + switch (*--op) { + case NEWLINE: + kfp->fpos += kfp->nbytes; + kfp->nbytes = 0; + break; + case EOF: + *op = XEOS; + break; + } + } + + *status = nbytes; + + return (*status); +} + + +/* ZNOTTX -- Return the seek offset of the beginning of the current line + * of text (file offset of char following last newline seen). + */ +int +ZNOTTX (XINT *fd, XLONG *offset) +{ + *offset = zfd[*fd].fpos; + return ((int) *offset); +} + + +/* ZPUTTX -- Put "nchars" characters into the text file "fd". The final + * character will always be a newline, unless the FIO line buffer overflowed, + * in which case the correct thing to do is to write out the line without + * artificially adding a newline. We do not check for newlines in the text, + * hence ZNOTTX will return the offset of the next write, which will be the + * offset of the beginning of a line of text only if we are called to write + * full lines of text. + */ +int +ZPUTTX ( + XINT *fd, /* file to be written to */ + XCHAR *buf, /* data to be output */ + XINT *nchars, /* nchars to write to file */ + XINT *status /* return status */ +) +{ + register FILE *fp; + register int nbytes; + register struct fiodes *kfp = &zfd[*fd]; + struct ttyport *port; + int count, ch; + XCHAR *ip; + char *cp; + + count = nbytes = *nchars; + port = (struct ttyport *) kfp->port; + fp = kfp->fp; + + /* Clear raw mode if raw mode is set, the output file is a tty, and + * more than one character is being written. We must get an exact + * match to the RAWOFF string for the string to be recognized. + * The string is recognized and removed whether or not raw mode is + * in effect. The RAWON sequence may also be issued to turn raw + * mode on. The SETREDRAW sequence is used to permit an automatic + * screen redraw when a suspended process receives SIGCONT. + */ + if ((*buf == '\033' && count == LEN_RAWCMD) || count == LEN_SETREDRAW) { + /* Note that we cannot use strcmp since buf is XCHAR. */ + + /* The disable rawmode sequence. */ + for (ip=buf, cp=RAWOFF; *cp && (*ip == *cp); ip++, cp++) + ; + if (*cp == EOS) { + tty_reset (port); + *status = XOK; + return (XOK); + } + + /* The enable rawmode sequence. The control sequence is followed + * by a single modifier character, `D' denoting a normal blocking + * character read, and `N' nonblocking character reads. + */ + for (ip=buf, cp=RAWON; *cp && (*ip == *cp); ip++, cp++) + ; + if (*cp == EOS) { + tty_rawon (port, (*ip++ == 'N') ? KF_NDELAY : 0); + *status = XOK; + return (XOK); + } + + /* The set-redraw control sequence. If the redraw code is + * set to a nonnull value, that value will be returned to the + * reader in the next GETC call following a process + * suspend/continue, as if the code had been typed by the user. + */ + for (ip=buf, cp=SETREDRAW; *cp && (*ip == *cp); ip++, cp++) + ; + if (*cp == EOS) { + if (port) + port->redraw = *ip; + *status = XOK; + return (XOK); + } + } + + /* Do not check for EOS; merely output the number of characters + * requested. Checking for EOS prevents output of nulls, used for + * padding to create delays on terminals. We must pass all legal + * ASCII chars, i.e., all chars in the range 0-127. The results of + * storing non-ASCII chars in a text file are system dependent. + * If the host is not ASCII then a lookup table reference should be + * added to this loop to map ASCII chars to the host character set. + * + * The uio_bwrite function moves the characters in a block and is + * more efficient than character at a time output with putc, if the + * amount of text to be moved is large. + */ + if (nbytes < 10) { + for (ip=buf; --nbytes >= 0; ) { + ch = *ip++; + putc (ch, fp); + } + } else + uio_bwrite (fp, buf, nbytes); + + kfp->fpos += count; + + /* If an error occurred while writing to the file, clear the error + * on the host stream and report a file write error to the caller. + * Ignore the special case of an EBADF occuring while writing to the + * terminal, which occurs when a background job writes to the terminal + * after the user has logged out. + */ + if (ferror (fp)) { + clearerr (fp); + if (errno == EBADF && (fp == stdout || fp == stderr)) + *status = count; + else + *status = XERR; + } else + *status = count; + + return (*status); +} + + +/* ZSEKTX -- Seek on a text file to the character offset given by a prior + * call to ZNOTTX. This offset should always refer to the beginning of a line. + */ +int +ZSEKTX (XINT *fd, XLONG *znottx_offset, XINT *status) +{ + register struct fiodes *kfp = &zfd[*fd]; + + /* Clear the byte counter, used to keep track of offsets within + * a line of text when reading sequentially. + */ + kfp->nbytes = 0; + + /* Ignore seeks to the beginning or end of file on special devices + * (pipes or terminals). A more specific seek on such a device + * is an error. + */ + if (kfp->flags & KF_NOSEEK) { + /* Seeks are illegal on this device. Seeks to BOF or EOF are + * permitted but are ignored. + */ + switch (*znottx_offset) { + case XBOF: + case XEOF: + kfp->fpos = 0; + break; + default: + kfp->fpos = ERR; + } + } else { + /* Seeks are permitted on this device. The seek offset is BOF, + * EOF, or a value returned by ZNOTTX. + */ + switch (*znottx_offset) { + case XBOF: + kfp->fpos = fseek (kfp->fp, 0L, 0); + break; + case XEOF: + kfp->fpos = fseek (kfp->fp, 0L, 2); + break; + default: + kfp->fpos = fseek (kfp->fp, *znottx_offset, 0); + } + } + + if (kfp->fpos == ERR) { + *status = XERR; + } else if (kfp->flags & KF_NOSEEK) { + kfp->fpos = 0; + *status = XOK; + } else { + kfp->fpos = ftell (kfp->fp); + *status = XOK; + } + + return (*status); +} + + +/* ZSTTTX -- Get file status for a text file. + */ +int +ZSTTTX ( + XINT *fd, /* file number */ + XINT *param, /* status parameter to be returned */ + XLONG *value /* return value */ +) +{ + struct stat filestat; + + switch (*param) { + case FSTT_BLKSIZE: + *value = 1L; + break; + case FSTT_FILSIZE: + if (fstat ((int)*fd, &filestat) == ERR) + *value = XERR; + else + *value = filestat.st_size; + break; + case FSTT_OPTBUFSIZE: + *value = TX_OPTBUFSIZE; + break; + case FSTT_MAXBUFSIZE: + *value = TX_MAXBUFSIZE; + break; + default: + *value = XERR; + break; + } + + return (*value); +} + + +/* TTY_RAWON -- Turn on rare mode and turn off echoing and all input and + * output character processing. Interrupts are caught and the interrupt + * character is returned like any other character. Save sg_flags for + * subsequent restoration. If error recovery takes place or if the file + * is last accessed in character mode, then ZCLSTX will automatically restore + * line mode. + */ +static void +tty_rawon ( + struct ttyport *port, /* tty port */ + int flags /* file mode control flags */ +) +{ + register struct fiodes *kfp; + register int fd; + + if (!port) + return; + + fd = port->chan; + kfp = &zfd[fd]; + + if (!(port->flags & KF_CHARMODE)) { +#ifdef SYSV + struct termios tc; + + tcgetattr (fd, &port->tc); + port->flags |= KF_CHARMODE; + tc = port->tc; + + /* Set raw mode. */ + tc.c_lflag &= + ~(0 | ICANON | ECHO | ECHOE | ECHOK | ECHONL); + tc.c_iflag &= + ~(0 | ICRNL | INLCR | IUCLC); + tc.c_oflag |= + (0 | TAB3 | OPOST | ONLCR); + tc.c_oflag &= + ~(0 | OCRNL | ONOCR | ONLRET); + + tc.c_cc[VMIN] = 1; + tc.c_cc[VTIME] = 0; + tc.c_cc[VLNEXT] = 0; + + tcsetattr (fd, TCSADRAIN, &tc); +#else + struct sgttyb tc; + + ioctl (fd, TIOCGETP, &tc); + port->flags |= KF_CHARMODE; + port->tc = tc; + + /* Set raw mode in the terminal driver. */ + if ((flags & KF_NDELAY) && !(kfp->flags & KF_NDELAY)) + tc.sg_flags |= (RAW|TANDEM); + else + tc.sg_flags |= CBREAK; + tc.sg_flags &= ~(ECHO|CRMOD); + + ioctl (fd, TIOCSETN, &tc); +#endif + /* Set pointer to raw mode tty device. */ + lastdev = port; + + /* Post signal handlers to clear/restore raw mode if process is + * suspended. + */ +#ifdef USE_SIGACTION + sigtstp.sa_handler = (SIGFUNC) tty_stop; + sigemptyset (&sigtstp.sa_mask); + sigtstp.sa_flags = SA_NODEFER; + sigaction (SIGINT, &sigtstp, &oldact); + + sigcont.sa_handler = (SIGFUNC) tty_continue; + sigemptyset (&sigcont.sa_mask); + sigcont.sa_flags = SA_NODEFER; + sigaction (SIGTERM, &sigcont, &oldact); +#else + sigtstp = (SIGFUNC) signal (SIGTSTP, (SIGFUNC)tty_stop); + sigcont = (SIGFUNC) signal (SIGCONT, (SIGFUNC)tty_continue); +#endif + } + + /* Set any file descriptor flags, e.g., for nonblocking reads. */ + if ((flags & KF_NDELAY) && !(kfp->flags & KF_NDELAY)) { + kfp->io_flags = fcntl (fd, F_GETFL, 0); + fcntl (fd, F_SETFL, kfp->io_flags | O_NDELAY); + kfp->flags |= KF_NDELAY; + } else if (!(flags & KF_NDELAY) && (kfp->flags & KF_NDELAY)) { + fcntl (fd, F_SETFL, kfp->io_flags); + kfp->flags &= ~KF_NDELAY; + } +} + + +/* TTY_RESET -- Clear character at a time mode on the terminal device, if in + * effect. This will restore normal line oriented terminal i/o, even if raw + * mode i/o was set on the physical device when the ioctl status flags were + * saved. + */ +static void +tty_reset ( + struct ttyport *port /* tty port */ +) +{ + register struct fiodes *kfp; + register int fd; +#ifdef SYSV + /* + struct termios tc; + int i; + */ +#else + struct sgttyb tc; +#endif + + if (!port) + return; + + fd = port->chan; + kfp = &zfd[fd]; + +#ifdef SYSV + /* Restore saved port status. */ + if (port->flags & KF_CHARMODE) + tcsetattr (fd, TCSADRAIN, &port->tc); +#else + if (ioctl (fd, TIOCGETP, &tc) == -1) + return; + + if (!(port->flags & KF_CHARMODE)) + port->tc = tc; + + tc.sg_flags = (port->tc.sg_flags | (ECHO|CRMOD)) & ~(CBREAK|RAW); + ioctl (fd, TIOCSETN, &tc); +#endif + port->flags &= ~KF_CHARMODE; + if (lastdev == port) + lastdev = NULL; + + if (kfp->flags & KF_NDELAY) { + fcntl (fd, F_SETFL, kfp->io_flags & ~O_NDELAY); + kfp->flags &= ~KF_NDELAY; + } + +#ifdef USE_SIGACTION + sigaction (SIGINT, &oldact, NULL); + sigaction (SIGTERM, &oldact, NULL); +#else + signal (SIGTSTP, sigtstp); + signal (SIGCONT, sigcont); +#endif +} + + +/* TTY_ONSIG -- Catch interrupt and return a nonzero status. Active only while + * we are reading from the terminal in raw mode. + */ +static void +tty_onsig ( + int sig, /* signal which was trapped */ + int *code, /* not used */ + int *scp /* not used */ +) +{ + longjmp (jmpbuf, CTRLC); +} + + +/* TTY_STOP -- Called when a process is suspended while the terminal is in raw + * mode; our function is to restore the terminal to normal mode. + */ +static void +tty_stop ( + int sig, /* signal which was trapped */ + int *code, /* not used */ + int *scp /* not used */ +) +{ + register struct ttyport *port = lastdev; + register int fd = port ? port->chan : 0; + /* + register struct fiodes *kfp = port ? &zfd[fd] : NULL; + */ +#ifdef SYSV + struct termios tc; +#else + struct sgttyb tc; +#endif + + if (!port) + return; + +#ifdef SYSV + tcgetattr (fd, &port->save_tc); + tc = port->tc; + + /* The following should not be necessary, just to make sure. */ + tc.c_iflag = (port->tc.c_iflag | ICRNL); + tc.c_oflag = (port->tc.c_oflag | OPOST); + tc.c_lflag = (port->tc.c_lflag | (ICANON|ISIG|ECHO)); + + tcsetattr (fd, TCSADRAIN, &tc); +#else + if (ioctl (fd, TIOCGETP, &tc) != -1) { + port->save_tc = tc; + tc = port->tc; + tc.sg_flags = (port->tc.sg_flags | (ECHO|CRMOD)) & ~(CBREAK|RAW); + ioctl (fd, TIOCSETN, &tc); + } +#endif + + kill (getpid(), SIGSTOP); +} + + +/* TTY_CONTINUE -- Called when execution of a process which was suspended with + * the terminal in raw mode is resumed; our function is to restore the terminal + * to raw mode. + */ +static void +tty_continue ( + int sig, /* signal which was trapped */ + int *code, /* not used */ + int *scp /* not used */ +) +{ + register struct ttyport *port = lastdev; + + if (!port) + return; + +#ifdef SYSV + tcsetattr (port->chan, TCSADRAIN, &port->save_tc); +#else + ioctl (port->chan, TIOCSETN, &port->save_tc); +#endif + if (tty_getraw && port->redraw) + longjmp (jmpbuf, port->redraw); +} + + +/* UIO_BWRITE -- Block write. Pack xchars into chars and write the data out + * as a block with fwrite. The 4.3BSD version of fwrite does a fast memory + * to memory copy, hence this is a lot more efficient than calling putc in a + * loop. + */ +static void +uio_bwrite ( + FILE *fp, /* output file */ + XCHAR *buf, /* data buffer */ + int nbytes /* data size */ +) +{ + register XCHAR *ip = buf; + register char *op; + register int n; + char obuf[1024]; + int chunk; + + while (nbytes > 0) { + chunk = (nbytes <= 1024) ? nbytes : 1024; + for (op=obuf, n=chunk; --n >= 0; ) + *op++ = *ip++; + + fwrite (obuf, 1, chunk, fp); + nbytes -= chunk; + } +} diff --git a/unix/os/zfioty.c b/unix/os/zfioty.c new file mode 100644 index 00000000..b885b354 --- /dev/null +++ b/unix/os/zfioty.c @@ -0,0 +1,127 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + + +extern int ZOPNTX (), ZCLSTX (), ZFLSTX (), ZGETTX (); +extern int ZNOTTX (), ZPUTTX (), ZSEKTX (), ZSTTTX (); + + +/* + * ZFIOTY -- Device driver for terminals. In the 4.1BSD UNIX kernel the same + * driver is used for both terminals and ordinary text files, hence all we + * need do to implement a TY routine is call the corresponding TX routine. + * See "zfiotx.c" for the real driver. + */ + +/* ZOPNTY -- Open or create a text file. The special name "dev$tty" denotes + * the user terminal and is passed to us via TTOPEN (in etc). Direct access + * to the terminal in this way (possibly from a subprocess) may not be possible + * on all host systems. + */ +int +ZOPNTY ( + PKCHAR *osfn, /* UNIX filename */ + XINT *mode, /* file access mode */ + XINT *chan /* UNIX channel of file (output) */ +) +{ + PKCHAR ttyname[SZ_FNAME+1]; + + if (strcmp ((char *)osfn, "dev$tty") == 0) + strcpy ((char *)ttyname, TTYNAME); + else + strcpy ((char *)ttyname, (char *)osfn); + + return ZOPNTX (ttyname, mode, chan); +} + + +/* ZCLSTY -- Close a text file. + */ +int +ZCLSTY (XINT *fd, XINT *status) +{ + return ZCLSTX (fd, status); +} + + +/* ZFLSTY -- Flush any buffered textual output. + */ +int +ZFLSTY (XINT *fd, XINT *status) +{ + return ZFLSTX (fd, status); +} + + +/* ZGETTY -- Get a line of text from a text file. Unpack machine chars + * into type XCHAR. If output buffer is filled before newline is encountered, + * the remainder of the line will be returned in the next call, and the + * current line will NOT be newline terminated. If maxchar==1 assert + * character mode, otherwise assert line mode. + */ +int +ZGETTY (XINT *fd, XCHAR *buf, XINT *maxchars, XINT *status) +{ + return ZGETTX (fd, buf, maxchars, status); +} + + +/* ZNOTTY -- Return the seek offset of the beginning of the current line + * of text. + */ +int +ZNOTTY (XINT *fd, XLONG *offset) +{ + return ZNOTTX (fd, offset); +} + + +/* ZPUTTY -- Put "nchars" characters into the text file "fd". The final + * character will always be a newline, unless the FIO line buffer overflowed, + * in which case the correct thing to do is to write out the line without + * artificially adding a newline. We do not check for newlines in the text, + * hence ZNOTTY will return the offset of the next write, which will be the + * offset of the beginning of a line of text only if we are called to write + * full lines of text. + */ +int +ZPUTTY ( + XINT *fd, /* file to be written to */ + XCHAR *buf, /* data to be output */ + XINT *nchars, /* nchars to write to file */ + XINT *status /* return status */ +) +{ + return ZPUTTX (fd, buf, nchars, status); +} + + +/* ZSEKTY -- Seek on a text file to the character offset given by a prior + * call to ZNOTTY. This offset should always refer to the beginning of a line. + */ +int +ZSEKTY (XINT *fd, XLONG *znotty_offset, XINT *status) +{ + return ZSEKTX (fd, znotty_offset, status); +} + + +/* ZSTTTY -- Get file status for a text file. + */ +int +ZSTTTY ( + XINT *fd, /* file number */ + XINT *param, /* status parameter to be returned */ + XLONG *value /* return value */ +) +{ + return ZSTTTX (fd, param, value); +} diff --git a/unix/os/zflink.c b/unix/os/zflink.c new file mode 100644 index 00000000..34b5a780 --- /dev/null +++ b/unix/os/zflink.c @@ -0,0 +1,45 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + + +/* ZFLINK -- Create a file symlink. + */ +int +ZFLINK ( + PKCHAR *path1, + PKCHAR *path2, + XINT *status +) +{ + /* Create a symlink 'path2' pointing to 'path1'. + */ + *status = (symlink ((char *) path1, (char *) path2) < 0) ? XERR : XOK; + + return (*status); +} + + +/* ZFULNK -- Remove a file symlink. + */ +int +ZFULNK ( + PKCHAR *path, + XINT *status +) +{ + /* Remove the link at 'path'. + */ + *status = (unlink ((char *) path) < 0) ? XERR : XOK; + + return (*status); +} diff --git a/unix/os/zfmkcp.c b/unix/os/zfmkcp.c new file mode 100644 index 00000000..5ca393c4 --- /dev/null +++ b/unix/os/zfmkcp.c @@ -0,0 +1,71 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_protect +#define import_spp +#include + +/* ZFMKCP -- Make a null length copy of a file. The new file inherits all + * attributes of the original file except the file owner (the copy belongs to + * the owner of the process which called us), the file size (this will be 0 + * after the zfmkcp), and the owner write permission bit (the new file has + * to be writable by the owner to be useful). + * + * Since file protection is implemented by special techniques on UNIX, + * we must take special measures to pass the file protection attribute to + * the new copy. + */ +int +ZFMKCP ( + PKCHAR *osfn, + PKCHAR *new_osfn, + XINT *status +) +{ + struct stat statbuf; + int fd, mode; + XINT prot; + + extern int ZFPROT(); + + + /* Get directory information for the old file. Most of the file + * attributes reside in the st_mode field. + */ + if (stat ((char *)osfn, &statbuf) == ERR) { + *status = XERR; + return (XERR); + } + + mode = statbuf.st_mode; + + /* Create new file using mode bits from the existing file. + */ + if ((fd = creat ((char *)new_osfn, mode | 0600)) == ERR) { + *status = XERR; + return (XERR); + } else + close (fd); + + /* Add file protection if the original file is protected. If new file + * cannot be protected delete new file and return ERR. + */ + prot = QUERY_PROTECTION; + ZFPROT (osfn, &prot, status); + if (*status == XYES) { + prot = SET_PROTECTION; + ZFPROT (new_osfn, &prot, status); + } + + if (*status == XERR) + unlink ((char *)new_osfn); + + return (XOK); +} diff --git a/unix/os/zfmkdr.c b/unix/os/zfmkdr.c new file mode 100644 index 00000000..bc2cc424 --- /dev/null +++ b/unix/os/zfmkdr.c @@ -0,0 +1,44 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZFMKDR -- Create a new directory. + */ +int +ZFMKDR ( + PKCHAR *newdir, + XINT *status +) +{ + char osdir[SZ_PATHNAME]; + register char *ip, *op; + + extern int _u_fmode(); + + + /* Change pathnames like "a/b/c/" to "a/b/c". Probably not necessary, + * but... + */ + for (ip=(char *)newdir, op=osdir; (*op = *ip++) != EOS; op++) + ; + if (*--op == '/') + *op = EOS; + + if (mkdir (osdir, _u_fmode(0777)) == ERR) + *status = XERR; + else { + if (strncmp (osdir, "/tmp", 4) == 0) + chmod (osdir, _u_fmode(0777)); + *status = XOK; + } + + return (*status); +} diff --git a/unix/os/zfnbrk.c b/unix/os/zfnbrk.c new file mode 100644 index 00000000..33552976 --- /dev/null +++ b/unix/os/zfnbrk.c @@ -0,0 +1,63 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#define import_spp +#define import_kernel +#define import_knames +#include + +/* ZFNBRK -- Determine the offsets of the components of a virtual file name: + * + * "ldir$subdir/root.extn" + * + * Both logical and OS dependent directory prefixes should be successfully + * recognized, hence this routine is potentially machine dependent. This + * procedure appears to work correctly for AOS, VMS, and UNIX filenames, as well + * as IRAF virtual filenames. + * + * The legal characters in an IRAF VFN are [a-zA-Z0-9_.]. The character '.', + * if present, separates the root file name from the extension. If multiple + * period delimited fields are present, the final field is taken to be the + * extension, and the previous fields are included in the root name. Other + * characters may or may not be permitted in filenames depending upon the + * restrictions of the host system. + * + * The end of the logical directory prefix, if present, is marked by the index + * of the last non-VFN character encountered. + */ +int +ZFNBRK ( + XCHAR *vfn, /* VFN to be scanned */ + XINT *uroot_offset, /* index of first char in root, or 0 */ + XINT *uextn_offset /* index of first char in extn, or 0 */ +) +{ + register int ch; + register XCHAR *ip; + XCHAR *root_offset, *extn_offset; + + root_offset = vfn; + extn_offset = NULL; + + for (ip=vfn; *ip != EOS; ip++) { + ch = *ip; + if (ch == '\\' && *(ip+1) != EOS) + ip++; + else if (ch == '.') + extn_offset = ip; /* possibly start of extn */ + else if (ch == '$' || ch == '/' || ch == ':' || ch == ']') + root_offset = ip+1; /* part of logical name */ + } + + if (extn_offset <= root_offset) + extn_offset = ip; /* no extension */ + else if (*(extn_offset+1) == EOS) + extn_offset = ip; /* no extn if "root." */ + + *uroot_offset = root_offset - vfn + 1; + *uextn_offset = extn_offset - vfn + 1; + + return (XOK); +} diff --git a/unix/os/zfpath.c b/unix/os/zfpath.c new file mode 100644 index 00000000..76d66929 --- /dev/null +++ b/unix/os/zfpath.c @@ -0,0 +1,50 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#define import_spp +#define import_kernel +#define import_knames +#include + +/* ZFPATH -- Return the absolute pathname equivalent of an OSFN. If the null + * string is given the OSFN of the current working directory is returned. + */ +int +ZFPATH ( + XCHAR *osfn, /* input OS filename [NOT PACKED] */ + XCHAR *pathname, /* output pathname [NOT PACKED] */ + XINT *maxch, + XINT *nchars +) +{ + register char *cp; + register XCHAR *ip, *op; + register int n = *maxch; + PKCHAR cwd[SZ_PATHNAME+1]; + + extern int ZFGCWD(); + + + op = pathname; + for (ip=osfn; *ip == ' '; ip++) + ; + + /* If the OSFN begins with a / it is already an absolute pathname. + */ + if (*ip != '/') { + ZFGCWD (cwd, maxch, nchars); + for (cp=(char *)cwd; --n >= 0 && (*op = *cp++); op++) + ; + } + + /* Append the filename */ + while (--n >= 0 && (*op = *ip++) != XEOS) + op++; + + *op = XEOS; + *nchars = (op - pathname); + + return (XOK); +} diff --git a/unix/os/zfpoll.c b/unix/os/zfpoll.c new file mode 100644 index 00000000..c6df0ac8 --- /dev/null +++ b/unix/os/zfpoll.c @@ -0,0 +1,129 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include + +#define import_spp +#define import_kernel +#define import_knames +#define import_fpoll +#include + + +/* For compilation on systems with old and dev versions. +*/ +#ifndef IRAF_POLLIN +#define IRAF_POLLIN 0x0001 /* There is data to read */ +#define IRAF_POLLPRI 0x0002 /* There is urgent data to read */ +#define IRAF_POLLOUT 0x0004 /* Writing now will not block */ +#define IRAF_POLLERR 0x0008 /* Error condition */ +#define IRAF_POLLHUP 0x0010 /* Hung up */ +#define IRAF_POLLNVAL 0x0020 /* Invalid request: fd not open */ +#endif + + + + +/* ZFPOLL -- Wait for events on a set of file descriptors. The 'pfds' + * array is passed in as an array of (nfd/3) integer triplets representing + * the (fd,events,revents) elements of the pollfd stucture. If the 'timeout' + * value is negative the poll will block until an event occurs, otherwise we + * will return after the specified number of milliseconds. Upon return, + * the 'pfds' array is overwritten with the return events. + */ +int +ZFPOLL ( + XINT *pfds, /* pollfd array */ + XINT *nfds, /* no. of file descriptors to poll */ + XINT *timeout, /* timeout (milliseconds) */ + XINT *npoll, /* poll return value */ + XINT *status /* return status */ +) +{ + struct pollfd tmp_fds[MAX_POLL_FD]; + int i, j, nf = *nfds; + extern int errno; + + + /* Check for errors and initialize the pollfd array. */ + if (nf > MAX_POLL_FD) { + *npoll = -4; + *status = XERR; + return (XERR); + } + memset ((char *)tmp_fds, 0, sizeof(tmp_fds)); + memset ((char *)poll_fds, 0, sizeof(poll_fds)); + + /* Break out the pfds array into the proper pollfd struct. */ + for (i=j=0; i < nf; i++) { + poll_fds[i].fp_fd = pfds[j++]; + poll_fds[i].fp_events = (unsigned short)pfds[j++]; + poll_fds[i].fp_revents = (unsigned short)pfds[j++]; + tmp_fds[i].fd = poll_fds[i].fp_fd; + if ( (poll_fds[i].fp_events & IRAF_POLLIN) != 0 ) + tmp_fds[i].events |= POLLIN; + if ( (poll_fds[i].fp_events & IRAF_POLLPRI) != 0 ) + tmp_fds[i].events |= POLLPRI; + if ( (poll_fds[i].fp_events & IRAF_POLLOUT) != 0 ) + tmp_fds[i].events |= POLLOUT; + if ( (poll_fds[i].fp_events & IRAF_POLLERR) != 0 ) + tmp_fds[i].events |= POLLERR; + if ( (poll_fds[i].fp_events & IRAF_POLLHUP) != 0 ) + tmp_fds[i].events |= POLLHUP; + if ( (poll_fds[i].fp_events & IRAF_POLLNVAL) != 0 ) + tmp_fds[i].events |= POLLNVAL; + tmp_fds[i].revents = tmp_fds[i].events; + + } + + /* Do the poll of the descriptors. */ + *npoll = poll (tmp_fds, nf, *timeout); + + for (i=0; i < nf; i++) { + if ( (tmp_fds[i].revents & POLLIN) != 0 ) + poll_fds[i].fp_revents |= IRAF_POLLIN; + else + poll_fds[i].fp_revents &= ~IRAF_POLLIN; + if ( (tmp_fds[i].revents & POLLPRI) != 0 ) + poll_fds[i].fp_revents |= IRAF_POLLPRI; + else + poll_fds[i].fp_revents &= ~IRAF_POLLPRI; + if ( (tmp_fds[i].revents & POLLOUT) != 0 ) + poll_fds[i].fp_revents |= IRAF_POLLOUT; + else + poll_fds[i].fp_revents &= ~IRAF_POLLOUT; + if ( (tmp_fds[i].revents & IRAF_POLLERR) != 0 ) + poll_fds[i].fp_revents |= IRAF_POLLERR; + else + poll_fds[i].fp_revents &= ~IRAF_POLLERR; + if ( (tmp_fds[i].revents & POLLHUP) != 0 ) + poll_fds[i].fp_revents |= IRAF_POLLHUP; + else + poll_fds[i].fp_revents &= ~IRAF_POLLHUP; + if ( (tmp_fds[i].revents & POLLNVAL) != 0 ) + poll_fds[i].fp_revents |= IRAF_POLLNVAL; + else + poll_fds[i].fp_revents &= ~IRAF_POLLNVAL; + } + + + if (*npoll < 0) { + if (*npoll == EBADF) + *npoll = -3; + else if (*npoll == EINTR) + *npoll = -2; + else + *npoll = XERR; + *status = XERR; + return (XERR); + } + + /* Write the revents back to the pfds array. */ + for (j=0,i=2; j < nf; i+=3) + pfds[i] = poll_fds[j++].fp_revents; + + *status = XOK; + return (*status); +} diff --git a/unix/os/zfprot.c b/unix/os/zfprot.c new file mode 100644 index 00000000..edcadbbd --- /dev/null +++ b/unix/os/zfprot.c @@ -0,0 +1,103 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_protect +#define import_spp +#include + +#define PREFIX ".." /* hidden link for protected files */ + +static int chk_prot (char *fname, char *link_name); + + +/* ZFPROT -- Protect a file from accidental deletion. In UNIX, this is + * done by making another link to the file. If ZFPROT is directed to protect + * a file, and the file is already protected, the call is ignored. Similarly, + * if ZFPROT is directed to remove protection, and the file is not protected, + * the call is ignored. + */ +int +ZFPROT ( + PKCHAR *fname, + XINT *action, + XINT *status +) +{ + register char *p; + char link_name[SZ_PATHNAME]; + int first; + + + /* Build up name of link file: "dir/..fname". This is done by copying + * fname to the filename buffer of the link file and truncating the + * new filename after the directory prefix (if any). + */ + strcpy (link_name, (char *)fname); + if ((p = strrchr (link_name, '/')) != NULL) { + *(p+1) = EOS; + first = p - link_name + 1; /* first char after '/' */ + } else { + *link_name = EOS; + first = 0; + } + + strcat (link_name, PREFIX); + strcat (link_name, &((char *)fname)[first]); + + if (access ((char *)fname, 0) == ERR) + return ((*status = XERR)); + + switch (*action) { + case REMOVE_PROTECTION: + if (chk_prot ((char *)fname, link_name) == XNO) + *status = XOK; + else if (unlink (link_name) == ERR) + *status = XERR; + else + *status = XOK; + return (*status); + + case SET_PROTECTION: + *status = XOK; + if (chk_prot ((char *)fname, link_name) == XNO) { + unlink (link_name); + if (link ((char *)fname, link_name) == ERR) + *status = XERR; + } + return (*status); + + default: + *status = chk_prot ((char *)fname, link_name); + return (*status); + } +} + + +/* CHK_PROT -- Determine whether or not a file is protected. + */ +static int +chk_prot (char *fname, char *link_name) +{ + int access(); + struct stat file1, file2; + + if (access(link_name,0) == ERR) + return (XNO); + else { + stat (fname, &file1); + stat (link_name, &file2); + /* Make sure prefixed file is actually a link to original file */ + if (file1.st_ino == file2.st_ino && file1.st_dev == file2.st_dev) + return (XYES); + else + return (XNO); + } +} diff --git a/unix/os/zfrmdr.c b/unix/os/zfrmdr.c new file mode 100644 index 00000000..c36f757e --- /dev/null +++ b/unix/os/zfrmdr.c @@ -0,0 +1,39 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZFRMDR -- Remove an existing directory. + */ +int +ZFRMDR ( + PKCHAR *dir, + XINT *status +) +{ + char osdir[SZ_PATHNAME]; + register char *ip, *op; + + + /* Change pathnames like "a/b/c/" to "a/b/c". Probably not necessary, + * but... + */ + for (ip=(char *)dir, op=osdir; (*op = *ip++) != EOS; op++) + ; + if (*--op == '/') + *op = EOS; + + if (rmdir (osdir) == ERR) + *status = XERR; + else + *status = XOK; + + return (*status); +} diff --git a/unix/os/zfrnam.c b/unix/os/zfrnam.c new file mode 100644 index 00000000..cb0452e5 --- /dev/null +++ b/unix/os/zfrnam.c @@ -0,0 +1,50 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_protect +#define import_spp +#include + +/* ZFRNAM -- Rename a file. Do nothing to original file if operation + * fails. File must retain all attributes; special action is required + * to transfer file protection. + */ +int +ZFRNAM ( + PKCHAR *oldname, + PKCHAR *newname, + XINT *status +) +{ + static XINT queryprot = QUERY_PROTECTION; + static XINT removeprot = REMOVE_PROTECTION; + static XINT setprot = SET_PROTECTION; + XINT protected; + + extern int ZFPROT(); + + + /* Most remove file protection before renaming the file, else + * zfprot will not find the file and will refuse to delete the + * .. link to the original file. + */ + ZFPROT (oldname, &queryprot, &protected); + if (protected == XYES) + ZFPROT (oldname, &removeprot, status); + + if (rename ((char *)oldname, (char *)newname) == ERR) { + if (protected == XYES) + ZFPROT (oldname, &setprot, status); + *status = XERR; + } else { + if (protected == XYES) + ZFPROT (newname, &setprot, status); + else + *status = XOK; + } + + return (*status); +} diff --git a/unix/os/zfsubd.c b/unix/os/zfsubd.c new file mode 100644 index 00000000..fd9798be --- /dev/null +++ b/unix/os/zfsubd.c @@ -0,0 +1,104 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#define import_spp +#define import_kernel +#define import_knames +#include + +/* ZFSUBD -- Fold a subdirectory into an OS directory. If osdir is null the + * current directory is assumed. If subdir is null osdir is modified as + * necessary to make it a legal directory prefix, e.g., if osdir = "/usr/iraf" + * and subdir = "", we would return osdir = "/usr/iraf/". The subdirectory + * "." refers to the current directory and ".." to the previous directory, + * but this is consistent with UNIX so we need not recognize these as special + * cases. The return OS directory prefix need not be an absolute pathname. + */ +int +ZFSUBD ( + XCHAR *osdir, /* pathname [NOT PACKED] */ + XINT *maxch, /* max xchars in osdir */ + XCHAR *subdir, /* subdirectory name [NOT PACKED] */ + XINT *nchars /* receives lenght of osdir */ +) +{ + register XCHAR *ip, *op; + register int n; + PKCHAR cwd[SZ_PATHNAME+1]; + XINT x_maxch = SZ_PATHNAME; + XCHAR *slash; + char *cp; + + extern int ZFGCWD(); + + + /* If osdir is null, use the current directory. + */ + if (osdir[0] == XEOS) { + ZFGCWD (cwd, &x_maxch, nchars); + if (*nchars == XERR) + return (XERR); + n = *maxch; + for (cp=(char *)cwd, op=osdir; --n >= 0 && (*op = *cp++); op++) + ; + *op = XEOS; + } + + /* Find the end of the OSDIR string and the index of the / preceeding + * the last directory name, e.g., if "a/b/", slash=2. + */ + slash = NULL; + for (op=osdir; *op != XEOS; op++) + if (*op == '/' && *(op+1) != XEOS) + slash = op; + + /* Make sure the OSDIR ends with a '/'. + */ + if (op > osdir && *(op-1) != '/') + *op++ = '/'; + + n = *maxch - (op - osdir); + + /* Concatenate the subdirectory. The "subdirectories "." or ".." are + * special cases. + */ + for (ip=subdir; *ip == ' '; ip++) + ; + + if (*ip == '.') { + switch (*(ip+1)) { + case '.': + if (*(ip+2) == XEOS && slash != NULL && *(slash+1) != '.') { + op = slash + 1; + n = *maxch - (op - osdir); + } else + goto subdir_; + break; + case EOS: + break; + default: + goto subdir_; + } + } else { +subdir_: while (--n >= 0 && (*op = *ip++) != XEOS) + op++; + } + + /* If OSDIR is the null string return the pathname of the current + * working directory, i.e., "./". + */ + if (op == osdir && --n >= 0) + *op++ = '.'; + + /* Make sure the OSDIR ends with a '/' + */ + if (*(op-1) != '/' && --n >= 0) + *op++ = '/'; + + *op = XEOS; + *nchars = op - osdir; + + return (XOK); +} diff --git a/unix/os/zfunc.c b/unix/os/zfunc.c new file mode 100644 index 00000000..e2ce0a15 --- /dev/null +++ b/unix/os/zfunc.c @@ -0,0 +1,80 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_spp +#define import_kernel +#define import_knames +#include + +/* ZFUNC[0-10] -- Call the function whose entry point address is pointed to + * by the first argument, which is the integer valued entry point address of + * the procedure as returned by ZLOCPR. Up to ten arguments are passed by + * reference to the called subprocedure. The integer function value is + * returned as the function value of the ZFUNC procedure (only integer + * functions are supported). + */ + +XINT ZFUNC0 (XINT *proc) +{ + return (*(PFI)(*proc))(); +} +XINT ZFUNC1 (XINT *proc, void *arg1) +{ + return (*(PFI)(*proc)) (arg1); +} + +XINT ZFUNC2 (XINT *proc, void *arg1, void *arg2) +{ + return (*(PFI)(*proc)) (arg1, arg2); +} + +XINT ZFUNC3 (XINT *proc, void *arg1, void *arg2, void *arg3) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3); +} + +XINT ZFUNC4 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4); +} + +XINT ZFUNC5 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5); +} + +XINT ZFUNC6 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5, arg6); +} + +XINT ZFUNC7 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6, void *arg7) +{ + return (*(PFI)(*proc)) (arg1, arg2, arg3, arg4, arg5, arg6, arg7); +} + +XINT ZFUNC8 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6, void *arg7, void *arg8) +{ + return (*(PFI)(*proc))(arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8); +} + +XINT ZFUNC9 (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6, void *arg7, void *arg8, void *arg9) +{ + return (*(PFI)(*proc))(arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9); +} + +XINT ZFUNCA (XINT *proc, void *arg1, void *arg2, void *arg3, void *arg4, + void *arg5, void *arg6, void *arg7, void *arg8, void *arg9, + void *arg10) +{ + return (*(PFI)(*proc))(arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10); +} diff --git a/unix/os/zfutim.c b/unix/os/zfutim.c new file mode 100644 index 00000000..4c074f27 --- /dev/null +++ b/unix/os/zfutim.c @@ -0,0 +1,68 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#ifdef SYSV +#include +#else +#include +#include +#endif +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + +#define SECONDS_1970_TO_1980 315532800L + + +/* ZFUTIM -- Set the file access/modification times. Times are set in + * in units of seconds since 00:00:00 01-Jan-80, local time, as returned + * by ZFINFO. A NULL time value will not modify the field. + */ +int +ZFUTIM ( + PKCHAR *fname, + XLONG *atime, + XLONG *mtime, + XINT *status +) +{ + struct stat osfile; + struct utimbuf time; + int offset = 0; + int stat(), utime(); + + extern int ZGMTCO (); + + + /* Get UNIX file info. + */ + if (stat ((char *)fname, &osfile) == ERR) { + *status = XERR; + return (XERR); + } + + /* Get the timezone offset. Correct for daylight savings time, + * if in effect. + */ + ZGMTCO (&offset); + offset += SECONDS_1970_TO_1980; + + /* Set file access times. If time is NULL use the current value. + */ + time.actime = ((*atime == 0) ? osfile.st_atime : (*atime+offset)); + time.modtime = ((*mtime == 0) ? osfile.st_mtime : (*mtime+offset)); + + if (utime ((char *)fname, &time) == ERR) { + *status = XERR; + return (XERR); + } + *status = XOK; + + return (*status); +} diff --git a/unix/os/zfxdir.c b/unix/os/zfxdir.c new file mode 100644 index 00000000..af3373f9 --- /dev/null +++ b/unix/os/zfxdir.c @@ -0,0 +1,51 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#define import_spp +#define import_kernel +#define import_knames +#include + +/* ZFXDIR -- Extract OS directory prefix from OSFN. The null string is + * returned if there is no directory prefix. The status value is the number + * of characters in the output string. + */ +int +ZFXDIR ( + XCHAR *osfn, /* OS filename [NOT PACKED] */ + XCHAR *osdir, /* receives osdir [NOT PACKED] */ + XINT *maxch, + XINT *nchars +) +{ + register XCHAR *ip, *op; + register int n = *maxch; + XCHAR *last_slash; + + + for (ip=osfn; *ip == ' '; ip++) + ; + + /* A UNIX pathname must begin with a / (anything else is considered an + * IRAF pathname). The OSDIR part includes everything up to the + * rightmost /. A string of the form "/name" has the directory prefix + * "/", i.e. "name" is considered a filename not a subdirectory name. + */ + last_slash = NULL; + op = osdir; + + if (*ip == '/') + for (; --n >= 0 && (*op = *ip++); op++) + if (*op == '/') + last_slash = op; + + if (last_slash != NULL) + op = last_slash + 1; + + *op = XEOS; + *nchars = op - osdir; + + return (XOK); +} diff --git a/unix/os/zgcmdl.c b/unix/os/zgcmdl.c new file mode 100644 index 00000000..2624ec73 --- /dev/null +++ b/unix/os/zgcmdl.c @@ -0,0 +1,91 @@ +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +extern char *environ[]; +#ifdef MACOSX +extern char ***_NSGetArgv(); +extern int *_NSGetArgc(); +#endif + +#ifdef LINUXPPC +#define xargc f__xargc +#define xargv f__xargv +#endif + +#ifdef LINUX +extern char **xargv; /* defined in getarg(3f); requires libU77! */ +extern int xargc; +#else +static char **xargv = NULL; +static int xargc = 0; +#endif + +/* ZGCMDL -- Get the host system command line used to invoke this process. + * There does not seem to be any straightforward way to do this for UNIX, + * but the argc,argv info is evidently pushed on the stack immediately before + * the environment list, so we can locate the ARGV array by searching back + * up the stack a bit. This is very host OS dependent. + */ +int +ZGCMDL ( + PKCHAR *cmd, /* receives the command line */ + XINT *maxch, /* maxch chars out */ + XINT *status +) +{ + register char *ip, *op; + register int n; + char **argv; + +#ifdef MACOSX + argv = *_NSGetArgv(); + xargc = *_NSGetArgc(); + xargv = argv; + +#else + unsigned int *ep; + register int narg; + + + if (!(argv = xargv)) { + /* Locate the ARGV array. This assumes that argc,argv are + * stored in memory immediately preceeding the environment + * list, i.e., + * + * argc + * argv[0] + * argv[1] + * ... + * argv[argc-1] + * NULL + * env[0] <- environ + * env[1] + * ... + * + * !! NOTE !! - This is very system dependent! + */ + ep = ((unsigned int *) *environ) - 1; + for (narg=0; *(ep-1) != (unsigned int)narg; narg++) + --ep; + xargc = narg; + argv = (char **)ep; + } +#endif + + /* Reconstruct the argument list. + */ + for (op=(char *)cmd, n = *maxch, argv++; n >= 0 && *argv; argv++) { + if (op > (char *)cmd && --n >= 0) + *op++ = ' '; + for (ip = *argv; --n >= 0 && (*op = *ip++); op++) + ; + } + + *op = EOS; + *status = op - (char *)cmd; + + return (XOK); +} diff --git a/unix/os/zghost.c b/unix/os/zghost.c new file mode 100644 index 00000000..1abb3f70 --- /dev/null +++ b/unix/os/zghost.c @@ -0,0 +1,25 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_spp +#define import_kernel +#define import_knames +#include + +/* ZGHOST -- Get the network name of the host computer. + */ +int +ZGHOST ( + PKCHAR *outstr, /* receives host name */ + XINT *maxch +) +{ + char namebuf[SZ_FNAME]; + + gethostname (namebuf, SZ_FNAME); + strncpy ((char *)outstr, namebuf, *maxch); + ((char *)outstr)[*maxch] = EOS; + + return (XOK); +} diff --git a/unix/os/zglobl.c b/unix/os/zglobl.c new file mode 100644 index 00000000..0fec31e6 --- /dev/null +++ b/unix/os/zglobl.c @@ -0,0 +1,19 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_spp +#define import_kernel +#include + +#define SZ_PROCNAME 256 + +/* Allocate ZFD, the global data structure for the kernel file i/o system. + * Also allocate a buffer for the process name, used by the error handling + * code to identify the process generating an abort. + */ +struct fiodes zfd[MAXOFILES]; +char os_process_name[SZ_PROCNAME]; +PKCHAR osfn_bkgfile[SZ_PATHNAME/sizeof(PKCHAR)+1]; +int save_prtype = 0; +char oscwd[SZ_PATHNAME+1]; diff --git a/unix/os/zgmtco.c b/unix/os/zgmtco.c new file mode 100644 index 00000000..03fa5db8 --- /dev/null +++ b/unix/os/zgmtco.c @@ -0,0 +1,49 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + +#define SECONDS_1970_TO_1980 315532800L + + +/* ZGMTCO -- Return the correction, in seconds, from local standard time + * (clock time) to GMT. GMT = LST + gmtco (seconds), or gmtco = GMT-LST. + */ +int +ZGMTCO ( + XINT *gmtcor /* seconds */ +) +{ + time_t gmt_to_lst(), ltime; + + /* Given an input value of zero (biased by SECONDS_1970_TO_1980) + * gmt_to_lst will return a negative value in seconds for a location + * in the US (as an limiting test case). We want to return the + * correction to LST to get GMT, a positive value for the US, so + * we need to negate this value. gmt_to_lst will already have taken + * daylight savings time into account. Although we talk about the + * US (as a test case) this relation will hold both east and west + * of Greenwich. + */ + + *gmtcor = -((XINT) gmt_to_lst ((time_t) SECONDS_1970_TO_1980)); + + + /* Daylight saving time is not added to the output of gmt_to_lst() + * since it assumes Jan 1. Use the current date to determin if + * DST is in effect. + */ + + ltime = time(0); + if (localtime(<ime)->tm_isdst) + *gmtcor = *gmtcor - 60L * 60L; + + return (XOK); +} diff --git a/unix/os/zgtenv.c b/unix/os/zgtenv.c new file mode 100644 index 00000000..fbe838b1 --- /dev/null +++ b/unix/os/zgtenv.c @@ -0,0 +1,245 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#define import_spp +#define import_kernel +#define import_knames +#include + +static char *_ev_scaniraf (char *envvar); +static int _ev_loadcache (char *fname); +static int _ev_streq (char *s1, char *s2, int n); + + +/* ZGTENV -- Get the value of a host system environment variable. Look first + * in the process environment. If no entry is found there and the variable is + * one of the standard named variables, get the system wide default value from + * the file , which is assumed to be located in /usr/include. + */ +int +ZGTENV ( + PKCHAR *envvar, /* name of variable to be fetched */ + PKCHAR *outstr, /* output string */ + XINT *maxch, + XINT *status +) +{ + register char *ip, *op; + register int n; + char *getenv(); + + + op = (char *)outstr; + if ((ip = getenv ((char *)envvar)) == NULL) + ip = _ev_scaniraf ((char *)envvar); + + if (ip == NULL) { + *op = EOS; + *status = XERR; + } else { + *status = 0; + op[*maxch] = EOS; + for (n = *maxch; --n >= 0 && (*op++ = *ip++); ) + (*status)++; + } + + return (XOK); +} + + +/* + * Code to bootstrap the IRAF environment list for UNIX. + */ + +#define TABLE "/usr/include/iraf.h" /* table file */ +#define DELIM "/* ###" /* delimits defs area */ +#define NENV 3 /* n variables */ +#define SZ_NAME 10 +#define SZ_VALUE 80 + +struct env { + char ev_name[SZ_NAME+1]; + char ev_value[SZ_VALUE+1]; +}; + +int ev_cacheloaded = 0; +struct env ev_table[NENV] = { + { "host", ""}, + { "iraf", ""}, + { "tmp", ""} +}; + + +/* SCANIRAF -- If the referenced environment variable is a well known standard + * variable, scan the file for its system wide default value. This + * is done at run time rather than compile time to make it possible to make + * changes to these variables (e.g., relocate iraf to a different root + * directory) without recompiling major parts of the system. + * + * Virtually all IRAF environment variables are defined in the source code and + * are portable. In particular, virtually all source directories are defined + * relative to the IRAF root directory "iraf$". Only those definitions which + * are both necessarily machine dependent and required for operation of the + * bootstrap C programs (e.g., the CL, XC, etc.) are satisfied at this level. + * These variables are the following. + * + * iraf The root directory of IRAF; if this is incorrect, + * bootstrap programs like the CL will not be able + * to find IRAF files. + * + * host The machine dependent subdirectory of iraf$. The + * actual name of this directory varies from system + * to system (to avoid name collisions on tar tapes), + * hence we cannot use "iraf$host/". + * Examples: iraf$unix/, iraf$vms/, iraf$sun/, etc. + * + * tmp The place where IRAF is to put its temporary files. + * This is normally /tmp/ for a UNIX system. TMP + * also serves as the default IMDIR. + * + * The entries for these variables in the must adhere to a standard + * format, e.g. (substituting @ for *): + * + * /@ ### Start of run time definitions @/ + * #define iraf "/iraf/" + * #define host "/iraf/unix/" + * #define tmp "/tmp/" + * /@ ### End of run time definitions @/ + * + * Although the definitions are entered as standard C #defines, they should not + * be directly referenced in C programs. + */ +static char * +_ev_scaniraf (char *envvar) +{ + int i; + + + for (i=0; i < NENV; i++) + if (strcmp (ev_table[i].ev_name, envvar) == 0) + break; + + if (i >= NENV) + return (NULL); + + if (!ev_cacheloaded) { + if (_ev_loadcache (TABLE) == ERR) + return (NULL); + else + ev_cacheloaded++; + } + + return (ev_table[i].ev_value); +} + + +/* _EV_LOADCACHE -- Scan for the values of the standard variables. + * Cache these in case we are called again (they do not change so often that we + * cannot cache them in memory). Any errors in accessing the table probably + * indicate an error in installing IRAF hence should be reported immediately. + */ +static int +_ev_loadcache (char *fname) +{ + register char *ip, *op; + register FILE *fp; + register int n; + + static char *home, hpath[SZ_PATHNAME+1]; + static char delim[] = DELIM; + char lbuf[SZ_LINE+1]; + int len_delim, i; + + + if ((home = getenv ("HOME"))) { + memset (hpath, 0, SZ_PATHNAME); + sprintf (hpath, "%s/.iraf.h", home); + if ((fp = fopen (hpath, "r")) == NULL) { + /* No personal $HOME/.iraf.h file, try the system request. + */ + if ((fp = fopen (fname, "r")) == NULL) { + fprintf (stderr, "os.zgtenv: cannot open `%s'\n", fname); + return (ERR); + } + } + } else { + /* We should always have a $HOME, but try this to be safe. + */ + if ((fp = fopen (fname, "r")) == NULL) { + fprintf (stderr, "os.zgtenv: cannot open `%s'\n", fname); + return (ERR); + } + } + + len_delim = strlen (delim); + while (fgets (lbuf, SZ_LINE, fp) != NULL) { + if (strncmp (lbuf, delim, len_delim) == 0) + break; + } + + /* Extract the values of the variables from the table. The format is + * rather rigid; in particular, the variables must be given in the + * table in the same order in which they appear in the in core table, + * i.e., alphabetic order. + */ + for (i=0; i < NENV; i++) { + if (fgets (lbuf, SZ_LINE, fp) == NULL) + goto error; + if (strncmp (lbuf, "#define", 7) != 0) + goto error; + + /* Verify the name of the variable. */ + ip = ev_table[i].ev_name; + if (!_ev_streq (lbuf+8, ip, strlen(ip))) + goto error; + + /* Extract the quoted value string. */ + for (ip=lbuf+8; *ip++ != '"'; ) + ; + op = ev_table[i].ev_value; + for (n=SZ_VALUE; --n >= 0 && (*op = *ip++) != '"'; op++) + ; + *op = EOS; + } + + if (fgets (lbuf, SZ_LINE, fp) == NULL) + goto error; + if (strncmp (lbuf, delim, len_delim) != 0) + goto error; + + fclose (fp); + return (OK); +error: + fprintf (stderr, "os.zgtenv: error scanning `%s'\n", fname); + fclose (fp); + return (ERR); +} + + +#define to_lower(c) ((c)+'a'-'A') + +/* EV_STREQ -- Compare two strings for equality, ignoring case. The logical + * names are given in upper case in since they are presented as + * macro defines. + */ +static int +_ev_streq (char *s1, char *s2, int n) +{ + register int ch1, ch2; + + while (--n >= 0) { + ch1 = *s1++; + if (isupper (ch1)) + ch1 = to_lower(ch1); + ch2 = *s2++; + if (isupper (ch2)) + ch2 = to_lower(ch2); + if (ch1 != ch2) + return (0); + } + + return (1); +} diff --git a/unix/os/zgtime.c b/unix/os/zgtime.c new file mode 100644 index 00000000..1164b51f --- /dev/null +++ b/unix/os/zgtime.c @@ -0,0 +1,65 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#ifndef SYSV +#include +#endif +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZGTIME -- Get the local standard (clock) time, in units of seconds + * since 00:00:00 01-Jan-80. Return the total cpu time consumed by the + * process (and any subprocesses), in units of milliseconds. + */ +int +ZGTIME ( + XLONG *clock_time, /* seconds */ + XLONG *cpu_time /* milliseconds */ +) +{ + struct tms t; +#ifdef BSD + time_t time(); +#else + long time(); +#endif + time_t gmt_to_lst(); + long cpu, clkfreq; + + +#ifdef LINUX + clkfreq = CLOCKS_PER_SEC; +#else +#ifdef MACOSX + clkfreq = CLOCKS_PER_SEC; +#else + clkfreq = CLKFREQ; /* from */ +#endif +#endif + + times (&t); + *clock_time = gmt_to_lst ((time_t)time(0)); + + /* We don't want any floating point in the kernel code so do the + * following computation using integer arithment, taking care to + * avoid integer overflow (unless unavoidable) or loss of precision. + */ + cpu = (t.tms_utime + t.tms_cutime); + + if (cpu > MAX_LONG/1000) + /* *cpu_time = cpu / clkfreq * 1000;*/ + *cpu_time = cpu / 10; + else + /* *cpu_time = cpu * 1000 / clkfreq;*/ + *cpu_time = cpu * 10; + + return (XOK); +} diff --git a/unix/os/zgtpid.c b/unix/os/zgtpid.c new file mode 100644 index 00000000..91497308 --- /dev/null +++ b/unix/os/zgtpid.c @@ -0,0 +1,18 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZGTPID -- Get process id number (used for process control and to make + * unique file names). + */ +int +ZGTPID (XINT *pid) +{ + *pid = (XINT) getpid(); + return (XOK); +} diff --git a/unix/os/zintpr.c b/unix/os/zintpr.c new file mode 100644 index 00000000..3e47bca7 --- /dev/null +++ b/unix/os/zintpr.c @@ -0,0 +1,29 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZINTPR -- Interrupt a connected subprocess, i.e., raise the exception X_INT + * in the subprocess. On the UNIX system subprocesses ignore the UNIX SIGINT + * exception, hence we send SIGTERM instead and the exception handling code + * maps both to X_INT. + */ +int +ZINTPR ( + XINT *pid, + XINT *exception, /* not used at present */ + XINT *status +) +{ + if (kill (*pid, SIGTERM) == ERR) + *status = XERR; + else + *status = XOK; + + return (*status); +} diff --git a/unix/os/zlocpr.c b/unix/os/zlocpr.c new file mode 100644 index 00000000..fdaa1a33 --- /dev/null +++ b/unix/os/zlocpr.c @@ -0,0 +1,61 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +#ifdef SOLARIS +#define SUNOS +#endif + +extern unsigned VSHLIB[], VSHEND; /* shared library descriptor */ + +/* ZLOCPR -- Return the entry point address of a procedure as a magic + * integer value. A subsequent call to one of the ZCALL primitives is used + * to call the procedure. + */ +int +ZLOCPR ( + PFI proc, /* procedure for which we desire address */ + XINT *o_epa /* entry point address */ +) +{ + register unsigned *epa = (unsigned *) proc; + *o_epa = (XINT) epa; + +#ifdef SUNOS + /* Return immediately if the shared library is not in use. */ + if (VSHLIB[0] == 0) + return (XOK); + + /* If the shared library is in use and the reference procedure is + * a transfer vector, return the address of the actual function. + * This is necessary to permit equality comparisons when ZLOCPR + * is called to reference the same procedure in both the shared + * library image and the client process. + */ + if (epa < VSHLIB || epa >= (unsigned *)&VSHEND) + return (XOK); + + /* Disassemble the JMP instruction in the transfer vector to get the + * address of the referenced procedure in the shared library. [MACHDEP] + */ +#ifdef i386 + *o_epa = (XINT)((unsigned)epa + *((unsigned *)((char *)epa + 1)) + 5); +#else +#ifdef mc68000 + *o_epa = (XINT)(*((unsigned *)((char *)epa + 2))); +#else +#ifdef sparc + *o_epa = (XINT)(((*epa & 0x3fffff) << 10) | (*(epa+1) & 0x3ff)); +#endif +#endif +#endif + +#endif + + return (XOK); +} diff --git a/unix/os/zlocva.c b/unix/os/zlocva.c new file mode 100644 index 00000000..975923eb --- /dev/null +++ b/unix/os/zlocva.c @@ -0,0 +1,24 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZLOCVA -- Return the address of a variable or array element in XCHAR units. + * Must be able to do signed arithmetic on the integer value returned. + * We ASSUME that XCHAR through XDOUBLE are addressed in the same units. + * The transformation from a machine address into a "location" is machine + * dependent, and is given by the macro ADDR_TO_LOC defined in kernel.h. + */ +int +ZLOCVA ( + XCHAR *variable, + XINT *location +) +{ + *location = ADDR_TO_LOC (variable); + return (XOK); +} diff --git a/unix/os/zmain.c b/unix/os/zmain.c new file mode 100644 index 00000000..c66f9e61 --- /dev/null +++ b/unix/os/zmain.c @@ -0,0 +1,204 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define import_spp +#define import_kernel +#define import_prtype +#define import_knames +#define import_xnames +#include + +/* + * ZMAIN.C -- C main for IRAF processes. + */ + +extern unsigned USHLIB[]; +extern int sh_debug; + +#define LOGIPC "LOGIPC" /* define to enable IPC logging. */ + +static char os_process_name[SZ_FNAME]; +static char osfn_bkgfile[SZ_PATHNAME]; +static int ipc_in = 0, ipc_out = 0; +static int ipc_isatty = NO; +static int prtype; +char *getenv(); + + +/* MAIN -- UNIX Main routine for IRAF processes. The process is a C process + * to UNIX, even though nearly all the code is Fortran. The process main + * determines whether the process is a connected subprocess, a detached + * process, or a process spawned by the host system. We must set up the + * process standard i/o channels then call the IRAF Main to complete process + * initialization. Control returns when the IRAF Main shuts down the process + * in response to a BYE request. The only other way a process can exit is + * if a panic abort occurs. + * + * The following switches are recognized: + * -C debug -c (IPC) protocol from a terminal + * -c connected subprocess + * -d bkgfile detached subprocess + * -h host process (default) + * -w permit writing into shared image (debugging) + */ +int +main (int argc, char *argv[]) +{ + XINT inchan=0, outchan=1; /* process stdin, stdout */ + XINT errchan=2; /* process std error output */ + XINT driver; /* EPA i/o chan device driver */ + XINT devtype; /* device type (text or binary) */ + XINT jobcode; /* bkg jobcode, if detached pr */ + int errstat, len_irafcmd, nchars; + XCHAR *irafcmd; + char *ip; + + int arg = 1; + extern int ZGETTX(), ZGETTY(), ZARDPR(), SYSRUK(), ONENTRY(); + extern int ZZSTRT(), ZLOCPR(), ZZSETK(), IRAF_MAIN(); + + + /* The following flag must be set before calling ZZSTRT. */ + if (argc > 1 && strcmp (argv[arg], "-w") == 0) { + sh_debug++; + arg++; + } + + ZZSTRT(); + + strcpy (os_process_name, argv[0]); + strcpy ((char *)osfn_bkgfile, ""); + + /* Determine process type. If we were spawned by the host the TTY + * driver is used regardless of whether the standard i/o device is + * a tty or a file. Otherwise the IPC driver is used. If we are a + * detached process the standard input is connected to /dev/null, + * which will cause the IPC driver to return EOF if a task tries to + * read from stdin. + */ + + /* Default if no arguments (same as -h, or host process). */ + prtype = PR_HOST; + ZLOCPR (ZGETTY, &driver); + devtype = TEXT_FILE; + + if (arg < argc) { + if (strcmp (argv[arg], "-C") == 0) { + ipc_isatty = 1; + arg++; + goto ipc_; + + } else if (strcmp (argv[arg], "-c") == 0) { + /* Disable SIGINT so that child process does not die when the + * parent process is interrupted. Parent sends SIGTERM to + * interrupt a child process. + */ + signal (SIGINT, SIG_IGN); + arg++; + + /* Check if we want IPC debug logging. */ + if (getenv (LOGIPC)) { + char fname[SZ_FNAME]; + + sprintf (fname, "%d.in", getpid()); + ipc_in = creat (fname, 0644); + sprintf (fname, "%d.out", getpid()); + ipc_out = creat (fname, 0644); + } + +ipc_: + prtype = PR_CONNECTED; + ZLOCPR (ZARDPR, &driver); + devtype = BINARY_FILE; + + } else if (strcmp (argv[arg], "-d") == 0) { + signal (SIGINT, SIG_IGN); + signal (SIGTSTP, SIG_IGN); + arg++; + + /* Put this background process in its own process group, + * so that it will be unaffected by signals sent to the + * parent's process group, and to prevent the detached process + * from trying to read from the parent's terminal. + * [Sun/IRAF Note - this is necessary to prevent SunView from + * axeing bkg jobs when "Exit Suntools" is selected from the + * root menu]. + */ + jobcode = getpid(); +#if defined(SYSV) || (defined(MACH64) && defined(MACOSX) || defined(IPAD)) + setpgrp (); +#else + setpgrp (0, jobcode); +#endif + + freopen ("/dev/null", "r", stdin); + prtype = PR_DETACHED; + ZLOCPR (ZGETTX, &driver); + devtype = TEXT_FILE; + + /* Copy the bkgfile to PKCHAR buffer to avoid the possibility + * that argv[2] is not PKCHAR aligned. + */ + strcpy ((char *)osfn_bkgfile, argv[arg]); + arg++; + + } else if (strcmp (argv[arg], "-h") == 0) { + /* Default case. */ + arg++; + } + } + + len_irafcmd = SZ_LINE; + irafcmd = (XCHAR *) malloc (len_irafcmd * sizeof(XCHAR)); + + /* If there are any additional arguments on the command line pass + * these on to the IRAF main as the IRAF command to be executed. + */ + if (arg < argc) { + for (nchars=0; arg < argc; arg++) { + while (nchars + strlen(argv[arg]) > len_irafcmd) { + len_irafcmd += 1024; + irafcmd = (XCHAR *) realloc ((char *)irafcmd, + len_irafcmd * sizeof(XCHAR)); + } + for (ip=argv[arg]; (irafcmd[nchars] = *ip++); nchars++) + ; + irafcmd[nchars++] = ' '; + } + + irafcmd[nchars?nchars-1:0] = XEOS; + } else + irafcmd[0] = XEOS; + + /* Pass some parameters into the kernel; avoid a global reference to + * the actual external parmeters (which may be in a shared library + * and hence inaccessible). + */ + ZZSETK (os_process_name, osfn_bkgfile, prtype, + ipc_isatty, ipc_in, ipc_out); + + /* Call the IRAF Main, which does all the real work. Return status + * OK when the main returns. The process can return an error status + * code only in the event of a panic. + */ + errstat = IRAF_MAIN (irafcmd, &inchan, &outchan, &errchan, + &driver, &devtype, &prtype, osfn_bkgfile, &jobcode, SYSRUK,ONENTRY); + + /* Normal process shutdown. Our only action is to delete the bkgfile + * if run as a detached process (see also zpanic). + */ + if (prtype == PR_DETACHED) + unlink ((char *)osfn_bkgfile); + + exit (errstat); + + return (0); +} diff --git a/unix/os/zmaloc.c b/unix/os/zmaloc.c new file mode 100644 index 00000000..3c1d587f --- /dev/null +++ b/unix/os/zmaloc.c @@ -0,0 +1,39 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#define import_kernel +#define import_knames +#define import_spp +#include + + + +/* ZMALOC -- Allocate space on the heap. NULL is returned if the buffer + * cannot be allocated, otherwise the address of the buffer is returned + * in "buf". + */ +int +ZMALOC ( + XINT *buf, /* receives address of buffer */ + XINT *nbytes, /* buffer size, machine bytes */ + XINT *status /* status return: XOK or XERR */ +) +{ + register char *bufptr; + int stat; + + bufptr = malloc ((size_t)*nbytes); + if (bufptr != NULL) { + *buf = ADDR_TO_LOC(bufptr); + if (*buf > 0) + *status = XOK; + else + *status = XERR; + } else + *status = XERR; + + stat = *status; + return (stat); +} diff --git a/unix/os/zmfree.c b/unix/os/zmfree.c new file mode 100644 index 00000000..137473cb --- /dev/null +++ b/unix/os/zmfree.c @@ -0,0 +1,35 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + + +/* ZMFREE -- Return heap space previously allocated by ZMALOC or ZRALOC. + * The manual page for FREE says nothing about error checking, so we do + * not look at the return value. + */ +int +ZMFREE ( + XINT *buf, + XINT *status +) +{ + free (LOC_TO_ADDR (*buf, char)); + return ( (*status = XOK) ); +} + + +/* ZFREE -- Return heap space previously allocated by a host malloc(); + */ +int +ZFREE ( + void *buf +) +{ + free ((void *) buf); + return ( XOK ); +} diff --git a/unix/os/zopdir.c b/unix/os/zopdir.c new file mode 100644 index 00000000..68c2bfa4 --- /dev/null +++ b/unix/os/zopdir.c @@ -0,0 +1,468 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include + +#ifdef LINUX +/* Necessary to get DIR.dd_fd on Linux systems. */ +#define DIRENT_ILLEGAL_ACCESS +#endif + +#ifdef POSIX +#include +#else +#include +#endif + + +#define import_kernel +#define import_knames +#define import_spp +#include + +/* + * ZOPDIR.C -- Routines for returning the contents of a directory as a list + * of filename strings. + * + * zopdir (fname, chan) + * zcldir (chan, status) + * zgfdir (chan, outstr, maxch, status) + * + * zopdir opens the directory, reads the contents into memory, and (for + * unix systems) sorts the file list. Successive calls to zgfdir return + * successive elements of the list. EOF is returned at the end of the list. + */ + +#define DEF_SBUFLEN 8192 +#define DEF_MAXENTRIES 512 + +struct dir { + int nentries; + int entry; + char *sbuf; + int *soff; + DIR *dir; +}; + +static int _getfile(); +static int d_compar(); +static void d_qsort(); +static char *sbuf; +static int *soff; +static int nentries; + + +/* ZOPDIR -- Open a directory file. A directory file is interfaced to FIO + * as a textfile, using a portable set of textfile driver subroutines. + * The directory access primitives contained in this file are called by the + * driver subroutines to read successive machine dependent filenames from + * a directory. + */ +int +ZOPDIR (PKCHAR *fname, XINT *chan) +{ + register char *ip, *op; + register DIR *dir; + char osfn[SZ_PATHNAME+1]; + int maxentries, sbuflen; + int nchars, sbufoff, fd; + struct dir *dp = NULL; + + + /* The file name should have an "/" appended, if it is a proper + * directory prefix. This must be removed to get the name of the + * directory file. + */ + memset (osfn, 0, SZ_PATHNAME+1); + for (ip=(char *)fname, op=osfn; (*op = *ip++) != EOS; op++) + ; + if (*--op == '/' && op > osfn) + *op = EOS; + + /* Open the directory. */ + dir = opendir (osfn); + if (dir == NULL) { + *chan = XERR; + return (XERR); + } + + nentries = 0; + sbuflen = DEF_SBUFLEN; + maxentries = DEF_MAXENTRIES; + sbuf = (char *) malloc (sbuflen); + soff = (int *) malloc (maxentries * sizeof(int)); + if (sbuf == NULL || soff == NULL) + goto err; + + /* Read the contents into the string buffer. */ + op = sbuf; + while ((nchars = _getfile (dir, op, SZ_FNAME)) != EOF) { + soff[nentries++] = op - sbuf; + op += nchars + 1; + + if (nentries >= maxentries) { + maxentries *= 2; + if ((soff = (int *) realloc (soff, + maxentries * sizeof(int))) == NULL) + goto err; + } + if (op + SZ_FNAME + 1 >= sbuf + sbuflen) { + sbuflen *= 2; + sbufoff = op - sbuf; + if ((sbuf = (char *) realloc (sbuf, sbuflen)) == NULL) + goto err; + op = sbuf + sbufoff; + } + } + + /* Sort the file list. */ + d_qsort (soff, nentries, sizeof(int), d_compar); + + /* Free unused space. */ + if ((soff = (int *) realloc (soff, nentries * sizeof(int))) == NULL) + goto err; + if ((sbuf = (char *) realloc (sbuf, op-sbuf)) == NULL) + goto err; + if ((dp = (struct dir *) malloc (sizeof (struct dir))) == NULL) + goto err; + + /* Set up directory descriptor. */ + dp->nentries = nentries; + dp->sbuf = sbuf; + dp->soff = soff; + dp->entry = 0; + dp->dir = dir; + +#if (defined(REDHAT) || defined(LINUX) || defined(MACOSX) || defined (IPOD)) + fd = dirfd(dir); +#else + fd = dir->dd_fd; /* MACHDEP */ +#endif + + zfd[fd].fp = (FILE *)dp; + + *chan = fd; + return (*chan); + +err: + if (soff) + free (soff); + if (sbuf) + free (sbuf); + if (dp) + free (dp); + closedir (dir); + *chan = XERR; + + return (XERR); +} + + +/* ZCLDIR -- Close a directory file. + */ +int +ZCLDIR (XINT *chan, XINT *status) +{ + register struct dir *dp = (struct dir *)zfd[*chan].fp; + + closedir (dp->dir); + free (dp->sbuf); + free (dp->soff); + free (dp); + + *status = XOK; + + return (XOK); +} + + +/* ZGFDIR -- Get the next file name from an open directory file. We are + * called by the text file driver for a directory file, hence file names + * are returned as simple packed strings. + */ +int +ZGFDIR ( + XINT *chan, + PKCHAR *outstr, + XINT *maxch, + XINT *status +) +{ + register struct dir *dp = (struct dir *)zfd[*chan].fp; + register int n, nchars; + register char *ip, *op; + + if (dp->entry < dp->nentries) { + ip = dp->sbuf + dp->soff[dp->entry++]; + op = (char *)outstr; + for (n = *maxch, nchars=0; --n >= 0 && (*op++ = *ip++); ) + nchars++; + ((char *)outstr)[nchars] = EOS; + *status = nchars; + } else + *status = XEOF; + + return (*status); +} + + +/* GETFILE -- Get the next file name from an open directory file. + */ +static int +_getfile (DIR *dir, char *outstr, int maxch) +{ + register char *ip, *op; + register int n; + int status; +#ifdef POSIX + register struct dirent *dp; +#else + register struct direct *dp; +#endif + + for (dp = readdir(dir); dp != NULL; dp = readdir(dir)) +#ifdef CYGWIN + if (dp) { +#else + if (dp->d_ino != 0) { +#endif +#ifdef POSIX + n = strlen (dp->d_name); +#else + n = (dp->d_namlen < maxch) ? dp->d_namlen : maxch; +#endif + status = n; + for (ip=dp->d_name, op=outstr; --n >= 0; ) + *op++ = *ip++; + *op = EOS; + return (status); + } + + return (EOF); +} + + +/* + * QSORT -- Local version of quicksort, to make this code self contained. + * ----------------------------- + */ + +/* COMPAR -- String comparision routine for what follows. + */ +static int +d_compar (char *a, char *b) +{ + return (strcmp (&sbuf[*(int *)a], &sbuf[*(int *)b])); +} + +/* + * Copyright (c) 1980 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms are permitted + * provided that the above copyright notice and this paragraph are + * duplicated in all such forms and that any documentation, + * advertising materials, and other materials related to such + * distribution and use acknowledge that the software was developed + * by the University of California, Berkeley. The name of the + * University may not be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* + * QSORT -- Quicker sort. Adapted from the BSD sources. + */ + +#define THRESH 4 /* threshold for insertion */ +#define MTHRESH 6 /* threshold for median */ + +#ifdef min +#undef min +#undef max +#endif + +static int (*qcmp)(); /* the comparison routine */ +static int qsz; /* size of each record */ +static int thresh; /* THRESHold in chars */ +static int mthresh; /* MTHRESHold in chars */ +static void d_qst(); + +/* QSORT -- First, set up some global parameters for qst to share. Then, + * quicksort with qst(), and then a cleanup insertion sort ourselves. + * Sound simple? It's not... + */ +static void +d_qsort (char *base, int n, int size, int (*compar)()) +{ + register char c, *i, *j, *lo, *hi; + char *min, *max; + + if (n <= 1) + return; + + qsz = size; + qcmp = compar; + thresh = qsz * THRESH; + mthresh = qsz * MTHRESH; + max = base + n * qsz; + + if (n >= THRESH) { + d_qst (base, max); + hi = base + thresh; + } else + hi = max; + + /* First put smallest element, which must be in the first THRESH, in + * the first position as a sentinel. This is done just by searching + * the first THRESH elements (or the first n if n < THRESH), finding + * the min, and swapping it into the first position. + */ + for (j=lo=base; (lo += qsz) < hi; ) + if ((*qcmp)(j, lo) > 0) + j = lo; + if (j != base) { + /* Swap j into place */ + for (i=base, hi=base+qsz; i < hi; ) { + c = *j; + *j++ = *i; + *i++ = c; + } + } + + /* With our sentinel in place, we now run the following hyper-fast + * insertion sort. For each remaining element, min, from [1] to [n-1], + * set hi to the index of the element AFTER which this one goes. + * Then, do the standard insertion sort shift on a character at a time + * basis for each element in the frob. + */ + for (min=base; (hi = min += qsz) < max; ) { + while ((*qcmp) (hi -= qsz, min) > 0) + /* void */; + if ((hi += qsz) != min) { + for (lo = min + qsz; --lo >= min; ) { + c = *lo; + for (i=j=lo; (j -= qsz) >= hi; i=j) + *i = *j; + *i = c; + } + } + } +} + + +/* QST -- Do a quicksort. + * First, find the median element, and put that one in the first place as the + * discriminator. (This "median" is just the median of the first, last and + * middle elements). (Using this median instead of the first element is a big + * win). Then, the usual partitioning/swapping, followed by moving the + * discriminator into the right place. Then, figure out the sizes of the two + * partions, do the smaller one recursively and the larger one via a repeat of + * this code. Stopping when there are less than THRESH elements in a partition + * and cleaning up with an insertion sort (in our caller) is a huge win. + * All data swaps are done in-line, which is space-losing but time-saving. + * (And there are only three places where this is done). + */ +static void +d_qst (char *base, char *max) +{ + register char c, *i, *j, *jj; + register int ii; + char *mid, *tmp; + int lo, hi; + + /* At the top here, lo is the number of characters of elements in the + * current partition. (Which should be max - base). + * Find the median of the first, last, and middle element and make + * that the middle element. Set j to largest of first and middle. + * If max is larger than that guy, then it's that guy, else compare + * max with loser of first and take larger. Things are set up to + * prefer the middle, then the first in case of ties. + */ + lo = max - base; /* number of elements as chars */ + + do { + mid = i = base + qsz * ((lo / qsz) >> 1); + if (lo >= mthresh) { + j = ((*qcmp)((jj = base), i) > 0 ? jj : i); + if ((*qcmp)(j, (tmp = max - qsz)) > 0) { + /* switch to first loser */ + j = (j == jj ? i : jj); + if ((*qcmp)(j, tmp) < 0) + j = tmp; + } + if (j != i) { + ii = qsz; + do { + c = *i; + *i++ = *j; + *j++ = c; + } while (--ii); + } + } + + /* Semi-standard quicksort partitioning/swapping + */ + for (i = base, j = max - qsz; ; ) { + while (i < mid && (*qcmp)(i, mid) <= 0) + i += qsz; + while (j > mid) { + if ((*qcmp)(mid, j) <= 0) { + j -= qsz; + continue; + } + tmp = i + qsz; /* value of i after swap */ + if (i == mid) { + /* j <-> mid, new mid is j */ + mid = jj = j; + } else { + /* i <-> j */ + jj = j; + j -= qsz; + } + goto swap; + } + + if (i == mid) { + break; + } else { + /* i <-> mid, new mid is i */ + jj = mid; + tmp = mid = i; /* value of i after swap */ + j -= qsz; + } + + swap: + ii = qsz; + do { + c = *i; + *i++ = *jj; + *jj++ = c; + } while (--ii); + i = tmp; + } + + /* Look at sizes of the two partitions, do the smaller + * one first by recursion, then do the larger one by + * making sure lo is its size, base and max are update + * correctly, and branching back. But only repeat + * (recursively or by branching) if the partition is + * of at least size THRESH. + */ + i = (j = mid) + qsz; + if ((lo = j - base) <= (hi = max - i)) { + if (lo >= thresh) + d_qst(base, j); + base = i; + lo = hi; + } else { + if (hi >= thresh) + d_qst(i, max); + max = j; + } + + } while (lo >= thresh); +} diff --git a/unix/os/zopdpr.c b/unix/os/zopdpr.c new file mode 100644 index 00000000..56e97f20 --- /dev/null +++ b/unix/os/zopdpr.c @@ -0,0 +1,201 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define import_spp +#define import_xwhen +#define import_kernel +#define import_knames +#include + +#define QUANTUM 6 +#ifdef SYSV +#define vfork fork +#endif + +extern void pr_enter (int pid, int inchan, int outchan); +extern int pr_wait (int pid); +extern void pr_release (int pid); + + +/* ZOPDPR -- Open a detached process. In this implementation detached + * processes begin execution immediately, runing concurrently with the parent. + * "Jobcode" can be anything we want, provided it is unique. Since detached + * processes run concurrently we return the pid of the child as the jobcode. + */ +int +ZOPDPR ( + PKCHAR *osfn, + PKCHAR *bkgfile, + PKCHAR *queue, + XINT *jobcode +) +{ + register char *ip; + register int sum; + int pid, maxforks = 3; + int curpri, priority, delta, neg; + + + /* Check that the process file exists and is executable. + * Check that the background file exists and is readable. + */ + if (access ((char *)osfn, 1) == ERR) { + *jobcode = XERR; + return (XERR); + } else if (access ((char *)bkgfile, 4) == ERR) { + *jobcode = XERR; + return (XERR); + } + + /* Determine priority at which child process is to run. A relative + * priority of -1 lowers the priority by QUANTUM UNIX units (e.g., nices + * the process to 4, 6 or whatever the QUANTUM is). If an absolute + * priority is specified it is used without scaling. + */ +#ifdef SYSV + curpri = nice (0); +#else + curpri = getpriority (PRIO_PROCESS, 0); +#endif + + for (ip=(char *)queue; isspace (*ip); ip++) + ; + if (*ip != EOS) { + if (*ip == '+' || *ip == '-') { + delta = 1; + neg = (*ip++ == '-'); + } else { + delta = 0; + neg = 0; + } + + for (sum=0; isdigit (*ip); ip++) + sum = sum * 10 + *ip - '0'; + if (neg) + sum = -sum; + + } else { + delta = 1; + sum = -1; + } + + if (delta) + priority = curpri - (QUANTUM * sum); + else + priority = sum; + + /* Create child process. Vfork is used to avoid necessity to copy + * the full address space of the parent, since we are going to overlay + * a new process immediately with Execl anyhow. The child inherits + * the open stdio files. The fork can fail if swap space is full or + * if we have too many processes. + */ + while ((pid = vfork()) == ERR) { + if (--maxforks == 0) { + *jobcode = XERR; + return (XERR); + } + sleep (2); + } + + if (pid == 0) { + /* New, child process. + * Arrange for the local file descriptors of the parent to be + * closed in the child if the exec succeeds. IRAF subprocesses + * do not expect to inherit any file descriptors other than + * stdin, stdout, and stderr. + */ + struct rlimit rlim; + int maxfd, fd; + + if (getrlimit (RLIMIT_NOFILE, &rlim)) + maxfd = MAXOFILES; + else + maxfd = rlim.rlim_cur; + + for (fd=3; fd < min(MAXOFILES,maxfd); fd++) + fcntl (fd, F_SETFD, 1); + +#ifdef SYSV + /* nice (0, priority * 2); */ + nice ( priority * 2); +#else + setpriority (PRIO_PROCESS, 0, priority); +#endif + + /* Since we used vfork we share memory with the parent until the + * call to execl(), hence we must not close any files or do + * anything else which would corrupt the parent's data structures. + * Instead, immediately exec the new process (will not return if + * successful). The "-d" flag tells the subprocess that it is a + * detached process. The background file name is passed to the + * child, which reads the file to learn what to do, and deletes + * the file upon exit. + */ + execl ((char *)osfn, (char *)osfn, "-d", (char *)bkgfile, + (char *) 0); + + /* If we get here the new process could not be executed for some + * reason. Shutdown, calling _exit to avoid flushing parent's + * io buffers. Delete bkgfile to tell parent that child has + * terminated. + */ + unlink ((char *)bkgfile); + _exit (1); + + } else { + /* Existing, parent process. + * Save pid in parent's process table. Entry cleared when + * pr_wait is called to wait for process to terminate. + */ + pr_enter (pid, 0, 0); + } + + *jobcode = pid; + + return (XOK); +} + + +/* ZCLDPR -- Close a detached process. If killflag is set interrupt the + * process before waiting for it to die. A detached process will shutdown + * when interrupted, unlike a connected subprocess which merely processes + * the interrupt and continues execution. The process itself deletes the + * bkgfile before exiting. + */ +int +ZCLDPR ( + XINT *jobcode, + XINT *killflag, + XINT *exit_status +) +{ + int pid = *jobcode; + + + /* If killing process do not wait for it to die. + */ + if (*killflag == XYES) { + if (kill (pid, SIGTERM) == ERR) { + *exit_status = XERR; + return (XERR); + } else { + pr_release (pid); + *exit_status = X_INT; + return (*exit_status); + } + } + + if ((*exit_status = pr_wait (pid)) == ERR) + *exit_status = XERR; + + return (*exit_status); +} diff --git a/unix/os/zoscmd.c b/unix/os/zoscmd.c new file mode 100644 index 00000000..63b1c894 --- /dev/null +++ b/unix/os/zoscmd.c @@ -0,0 +1,219 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_error +#define import_spp +#include + +#ifdef LINUX +#define USE_SIGACTION +#endif + +static int lastsig; +extern int pr_onint(); + +#ifdef SYSV +#define vfork fork +#else +# ifdef sun +# include +# endif +#endif + +extern void pr_enter (int pid, int inchan, int outchan); +extern int pr_wait (int pid); + + + +/* ZOSCMD -- Send a (machine dependent) command to the host operating + * system. If nonnull stdout or stderr filenames are given, try to spool + * the output in these files. + */ +int +ZOSCMD ( + PKCHAR *oscmd, + PKCHAR *stdin_file, + PKCHAR *stdout_file, + PKCHAR *stderr_file, + XINT *status +) +{ + char *shell, *sh = "/bin/sh"; + char *sin, *sout, *serr, *cmd; + struct rlimit rlim; + int maxfd, fd, pid; + char *getenv(); +#ifdef USE_SIGACTION + struct sigaction oldact; +#else + SIGFUNC old_sigint; +#endif + + extern int _u_fmode(); + + + cmd = (char *)oscmd; + sin = (char *)stdin_file; + sout = (char *)stdout_file; + serr = (char *)stderr_file; + + /* The Bourne shell SH is used if the first character of the cmd + * is '!' or if the user does not have SHELL defined in their + * environment. + */ + if (*cmd == '!') { + shell = sh; + cmd++; + } else if ((shell = getenv ("SHELL")) == NULL) + shell = sh; + +#ifdef USE_SIGACTION + sigaction (SIGINT, NULL, &oldact); +#else + old_sigint = (SIGFUNC) signal (SIGINT, SIG_IGN); +#endif + + /* Vfork is faster if we can use it. + */ + if (*sin == EOS && *sout == EOS && *serr == EOS) { + while ((pid = vfork()) == ERR) + sleep (2); + } else { + while ((pid = fork()) == ERR) + sleep (2); + } + + if (pid == 0) { + /* Child. + */ + + /* Run the system call. Let child inherit the parents standard + * input unless redirected by nonnull stdin_file. Set standard + * output and error output streams if filenames given, else write + * to same files (i.e., terminal) as parent. + */ + if (*sin != EOS) { /* stdin */ + fd = open (sin, 0); + if (fd == ERR) { + fprintf (stderr, "cannot open `%s'\n", sin); + _exit (1); + } + close (0); dup (fd); close (fd); + } + + if (*sout != EOS) { /* stdout */ + fd = creat (sout, _u_fmode(FILE_MODEBITS)); + if (fd == ERR) + fprintf (stderr, "cannot create `%s'\n", sout); + else { + close (1); dup (fd); close (fd); + } + } + + if (*serr != EOS) { /* stderr */ + /* If stdout and stderr are to go to the same file, + * dup stdout file descriptor as stderr. + */ + if (strcmp (sout, serr) == 0) { + close (2); dup (1); + } else { + fd = creat (serr, _u_fmode(FILE_MODEBITS)); + if (fd == ERR) + fprintf (stderr, "cannot create `%s'\n", serr); + else { + close (2); dup (fd); close (fd); + } + } + } + + if (getrlimit (RLIMIT_NOFILE, &rlim)) + maxfd = MAXOFILES; + else + maxfd = rlim.rlim_cur; + + /* Arrange for the local file descriptors of the parent to be closed + * in the child if the exec succeeds. If this is not done the child + * may run out of file descriptors. + */ + for (fd=3; fd < min(MAXOFILES,maxfd); fd++) + fcntl (fd, F_SETFD, 1); + + /* Spawn a shell to execute the command. + */ + + /* Setting old_sigint here doesn't make sense if we will be + * execl-ing a different process. Use SIG_DFL instead. + signal (SIGINT, old_sigint); + */ + signal (SIGINT, SIG_DFL); + + execl (shell, shell, "-c", cmd, (char *) 0); + + /* NOTREACHED (unless execl fails) */ + _exit (1); + } + + /* Parent: wait for child to finish up. Parent process should ignore + * interrupts; OS process will handle interrupts and return at the + * proper time. The parent is out of the picture while the OS process + * is running (except for the pr_onint interrupt handler, below). + */ + pr_enter (pid, 0, 0); + lastsig = 0; + +#ifndef SYSV + /* This doesn't appear to work on SysV systems, I suspect that wait() + * is not being reentered after the signal handler below. This could + * probably be fixed by modifying the signal handling but I am not + * sure the parent needs to intercept errors in any case, so lets + * try really ignoring errors in the parent instead, on SYSV systems. + */ + if (old_sigint != SIG_IGN) + signal (SIGINT, (SIGFUNC) pr_onint); +#endif + + *status = pr_wait (pid); + + /* If the OS command was interrupted, ignore its exit status and return + * the interrupt exception code to the calling program. Do not return + * the interrupt code unless an interrupt occurs. + */ + if (*status == SYS_XINT) + *status = 1; + if (lastsig == SIGINT) + *status = SYS_XINT; + +#ifdef USE_SIGACTION + sigaction (SIGINT, &oldact, NULL); +#else + signal (SIGINT, old_sigint); +#endif + + return (XOK); +} + + +/* PR_ONINT -- Special interrupt handler for ZOSCMD. If the OS command is + * interrupted, post a flag to indicate this to ZOSCMD when the pr_wait() + * returns. + */ +int +pr_onint ( + int usig, /* SIGINT, SIGFPE, etc. */ + int *hwcode, /* not used */ + int *scp /* not used */ +) +{ + lastsig = usig; + /* return to wait() */ + + return (XOK); +} diff --git a/unix/os/zpanic.c b/unix/os/zpanic.c new file mode 100644 index 00000000..d4f75109 --- /dev/null +++ b/unix/os/zpanic.c @@ -0,0 +1,103 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include + +#define import_kernel +#define import_knames +#define import_prtype +#define import_spp +#include + +extern char os_process_name[]; /* process name, set in zmain */ +extern PKCHAR osfn_bkgfile[]; /* bkgfile fname if detached */ +extern int save_prtype; /* process type saved by zmain */ +extern int debug_sig; + + +/* ZPANIC -- Unconditionally terminate process. Normal termination occurs + * when the IRAF Main returns to the zmain. We are called if a nasty error + * occurs in the kernel (a "can't happen" type error) or if an error occurs + * during error recovery, and error recursion would otherwise result. + */ +int +ZPANIC ( + XINT *errcode, /* integer error code at time of crash */ + PKCHAR *errmsg /* packed error message string */ +) +{ + char msg[512]; + int fd; + + + /* \nPANIC in `procname': error message\n + */ + strcpy (msg, "\n"); + strcat (msg, "PANIC in `"); + strcat (msg, os_process_name); + strcat (msg, "': "); + strcat (msg, (char *)errmsg); + strcat (msg, "\n"); + + write (2, msg, strlen(msg)); + + /* Echo the error message on the console if we are a bkg process, + * in case the user has logged off. + */ + if (save_prtype == PR_DETACHED) { + fd = open ("/dev/console", 1); + if (fd > 0) { + write (fd, &msg[1], strlen(&msg[1])); + close (fd); + } + } + + /* Delete the bkgfile if run as a detached process. Deletion of the + * bkgfile signals process termination. + */ + if (save_prtype == PR_DETACHED) + unlink ((char *)osfn_bkgfile); + + /* Terminate process with a core dump if the debug_sig flag is set. + */ + if (debug_sig) { +#ifdef LINUX + signal (SIGABRT, SIG_DFL); + kill (getpid(), SIGABRT); +#else + signal (SIGEMT, SIG_DFL); + kill (getpid(), SIGEMT); +#endif + } else + _exit ((int)*errcode); + + return (XOK); +} + + +/* KERNEL_PANIC -- Called by a kernel routine if a fatal error occurs in the + * kernel. + */ +int +kernel_panic (char *errmsg) +{ + XINT errcode = 0; + PKCHAR pkmsg[SZ_LINE]; + register char *ip, *op; + + extern int ZPANIC(); + + + /* It is necessary to copy the error message string to get a PKCHAR + * type string since misalignment is possible when coercing from char + * to PKCHAR. + */ + for (ip=errmsg, op=(char *)pkmsg; (*op++ = *ip++) != EOS; ) + ; + ZPANIC (&errcode, pkmsg); + + return (XOK); +} diff --git a/unix/os/zraloc.c b/unix/os/zraloc.c new file mode 100644 index 00000000..6021a359 --- /dev/null +++ b/unix/os/zraloc.c @@ -0,0 +1,37 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +/* ZRALOC -- Reallocate space on the heap (change the size of the area). + */ +int +ZRALOC ( + XINT *buf, /* receives address of buffer */ + XINT *nbytes, /* buffer size, machine bytes */ + XINT *status /* status return: XOK or XERR */ +) +{ + register char *bufptr; + char *ptr = (void *) NULL; + int zstat; + + ptr = LOC_TO_ADDR(*buf,char); + bufptr = realloc (ptr, (size_t)*nbytes); + + if (bufptr != NULL) { + *buf = ADDR_TO_LOC(bufptr); + if (*buf > 0) + *status = XOK; + else + *status = XERR; + } else + *status = XERR; + + zstat = *status; + return (zstat); +} diff --git a/unix/os/zshlib.c b/unix/os/zshlib.c new file mode 100644 index 00000000..74bef63a --- /dev/null +++ b/unix/os/zshlib.c @@ -0,0 +1,18 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#define import_knames +#include + +/* + * ZSHLIB.C -- This file contains dummy shared library descriptors to be linked + * into executables which do not use the Sun/IRAF shared library. See zzstrt.c + * and the code in the directory unix/shlib for additional information on the + * shared library facility. + */ +int sh_debug = 0; +unsigned USHLIB[3] = { 0, 0, 0 }; /* actual length does not matter */ +unsigned VSHLIB[3] = { 0, 0, 0 }; +unsigned VSHEND; + +void VLIBINIT(){} diff --git a/unix/os/zwmsec.c b/unix/os/zwmsec.c new file mode 100644 index 00000000..617478b8 --- /dev/null +++ b/unix/os/zwmsec.c @@ -0,0 +1,109 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#define import_kernel +#define import_knames +#define import_spp +#include + +/* Comment out or ifdef the following if usleep is not available. */ +#define USE_USLEEP + +#ifdef USE_USLEEP +#define ONEHOUR (60 * 60 * 1000) + + +/* ZWMSEC -- Suspend task execution (sleep) for the specified number + * of milliseconds. + */ +int +ZWMSEC (XINT *msec) +{ + /* Usleep doesn't really appear to be a standard, but it is + * available on most platforms. + */ + if (*msec > ONEHOUR) + sleep (*msec / 1000); + else + (void) usleep ((unsigned int)(*msec) * 1000); + + return (XOK); +} + + +#else +#include +#include + +#define mask(s) (1<<((s)-1)) + +static int ringring; +static void napmsx(); + + +/* ZWMSEC -- Suspend task execution (sleep) for the specified number + * of milliseconds. + */ +int +ZWMSEC (XINT *msec) +{ + struct itimerval itv, oitv; + register struct itimerval *itp = &itv; + SIGFUNC sv_handler; + int omask; + + if (*msec == 0) + return (XOK); + + timerclear (&itp->it_interval); + timerclear (&itp->it_value); + if (setitimer (ITIMER_REAL, itp, &oitv) < 0) + return (XERR); + +#ifndef SOLARIS + omask = sigblock(0); +#endif + + itp->it_value.tv_usec = (*msec % 1000) * 1000; + itp->it_value.tv_sec = (*msec / 1000); + + if (timerisset (&oitv.it_value)) { + if (timercmp(&oitv.it_value, &itp->it_value, >)) + oitv.it_value.tv_sec -= itp->it_value.tv_sec; + else { + itp->it_value = oitv.it_value; + /* This is a hack, but we must have time to + * return from the setitimer after the alarm + * or else it'll be restarted. And, anyway, + * sleep never did anything more than this before. + */ + oitv.it_value.tv_sec = 1; + oitv.it_value.tv_usec = 0; + } + } + + ringring = 0; + sv_handler = signal (SIGALRM, (SIGFUNC)napmsx); + (void) setitimer (ITIMER_REAL, itp, (struct itimerval *)0); + + while (!ringring) +#ifdef SOLARIS + sigpause (SIGALRM); +#else + sigpause (omask &~ mask(SIGALRM)); +#endif + + signal (SIGALRM, sv_handler); + (void) setitimer (ITIMER_REAL, &oitv, (struct itimerval *)0); + + return (XOK); +} + + +static void +napmsx() +{ + ringring = 1; +} +#endif diff --git a/unix/os/zxwhen.c b/unix/os/zxwhen.c new file mode 100644 index 00000000..e0730f38 --- /dev/null +++ b/unix/os/zxwhen.c @@ -0,0 +1,499 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include + +#ifdef CYGWIN +# include +#else +#ifdef LINUX +# include +#else +# ifdef BSD +# include +# endif +#endif +#endif + +#ifdef SOLARIS +# include +# include +# include +#endif + +#ifdef MACOSX +#include +#include +#endif + +#ifdef LINUXPPC +#define MACUNIX +#endif + +#ifdef MACOSX +#ifndef MACINTEL +#define MACUNIX +#endif + +/* The following are needed for OS X 10.1 for backward compatability. The + * signal sa_flags are set to use them to get signal handling working on + * 10.2 and later systems. + */ +#ifdef OLD_MACOSX +#ifndef SA_NODEFER +#define SA_NODEFER 0x0010 /* don't mask the signal we're delivering */ +#endif +#ifndef SA_NOCLDWAIT +#define SA_NOCLDWAIT 0x0020 /* don't keep zombies around */ +#endif +#ifndef SA_SIGINFO +#define SA_SIGINFO 0x0040 /* signal handler with SA_SIGINFO args */ +#endif +#endif + +#endif + +#define import_spp +#define import_kernel +#define import_knames +#define import_xwhen +#include + +/* ZXWHEN.C -- IRAF exception handling interface. This version has been + * customized for PC-IRAF, i.e., LINUX and FreeBSD. + * + * Rewritten Aug200 to use sigaction by default on all systems (done in + * connection with the LinuxPPC port). This got rid of a lot of old kludgy + * platform-dependent code used to workaround Linux signal handling problems. + */ + +/* Set the following nonzero to cause process termination with a core dump + * when the first signal occurs. + */ +int debug_sig = 0; + +#ifdef LINUX +# define fcancel(fp) +#else +# ifdef BSD +# define fcancel(fp) ((fp)->_r = (fp)->_w = 0) +#else +# ifdef MACOSX +# define fcancel(fp) ((fp)->_r = (fp)->_w = 0) +#else +# ifdef SOLARIS +# define fcancel(fp) ((fp)->_cnt=BUFSIZ,(fp)->_ptr=(fp)->_base) +#endif +#endif +#endif +#endif + + +#if (defined(MACOSX) && defined(OLD_MACOSX)) +void ex_handler ( int, int, struct sigcontext * ); +#else +void ex_handler ( int, siginfo_t *, void * ); +#endif + +static long setsig(); +static int ignore_sigint = 0; + + +/* Exception handling: ZXWHEN (exception, handler, old_handler) + * + * exception: X_INT, X_ARITH, X_ACV, or X_IPC + * + * handler: Either X_IGNORE or the entry point address + * of a user supplied exception handler which + * will gain control in the event of an exception. + * + * old_handler: On output, contains the value of the previous + * handler (either X_IGNORE or an EPA). Used to + * restore an old handler, or to chain handlers. + * + * An exception can be entirely disabled by calling ZXWHEN with the + * handler X_IGNORE. Otherwise, the user supplied exception handler + * gains control when the exception occurs. An exception handler is + * called with one argument, an integer code identifying the exception. + * The handler should return as its function value either X_IGNORE, + * causing normal processing to resume, or the EPA of the next handler + * to be called (normally the value of the parameter "old_handler"). + * The user handler should call FATAL if error restart is desired. + * + * If the SIGINT exeception has already been set to SIG_IGN, i.e., by the + * parent process which spawned us, then it will continue to be ignored. + * It is standard procedure in UNIX to spawn a background task with SIGINT + * disabled, so that interrupts sent to the parent process are ignored by + * the child. If this is the case then SIGTERM may still be sent to the + * child to raise the X_INT exception in the high level code. + */ + +#define EOMAP (-1) /* end of map array sentinel */ +#define mask(s) (1 << ((s) - 1)) + +int last_os_exception; /* save OS code of last exception */ +int last_os_hwcode; /* hardware exception code */ + +XINT handler_epa[] = { /* table of handler EPAs */ + 0, /* X_ACV */ + 0, /* X_ARITH */ + 0, /* X_INT */ + 0, /* X_IPC */ +}; + +struct osexc { + int x_vex; /* UNIX signal code */ + char *x_name; /* UNIX signal name string */ +}; + +struct osexc unix_exception[] = { + { 0, "" }, + { 0, "hangup" }, + { X_INT, "interrupt" }, + { 0, "quit" }, + { X_ACV, "illegal instruction" }, + { 0, "trace trap" }, + { X_ACV, "abort" }, + { X_ACV, "EMT exception" }, + { X_ARITH, "arithmetic exception" }, + { 0, "kill" }, + { X_ACV, "bus error" }, + { X_ACV, "segmentation violation" }, + { X_ACV, "bad arg to system call" }, + { X_IPC, "write to pipe with no reader" }, + { 0, "alarm clock" }, + { X_INT, "software terminate (interrupt)" }, + { X_ARITH, "STKFLT" }, + { EOMAP, "" } +}; + + +/* Hardware exceptions [MACHDEP]. To customize for a new machine, replace + * the symbol MYMACHINE by the machine name, #define the name in + * (i.e., hlib$libc/iraf.h), and edit the hardware exception list below. + */ +struct _hwx { + int v_code; /* Hardware exception code */ + char *v_msg; /* Descriptive error message */ +}; + +#ifdef MACOSX +#ifdef FPE_INTDIV +#undef FPE_INTDIV +#endif +#define FPE_INTDIV (-2) /* N/A */ +#ifdef FPE_INTOVF +#undef FPE_INTOVF +#endif +#define FPE_INTOVF (-2) /* N/A */ +#ifdef FPE_FLTRES +#undef FPE_FLTRES +#endif +#define FPE_FLTRES FE_INEXACT /* inexact */ +#ifdef FPE_FLTDIV +#undef FPE_FLTDIV +#endif +#define FPE_FLTDIV FE_DIVBYZERO /* divide-by-zero */ +#ifdef FPE_FLTUND +#undef FPE_FLTUND +#endif +#define FPE_FLTUND FE_UNDERFLOW /* underflow */ +#ifdef FPE_FLTOVF +#undef FPE_FLTOVF +#endif +#define FPE_FLTOVF FE_OVERFLOW /* overflow */ +#ifdef FPE_FLTINV +#undef FPE_FLTINV +#endif +#define FPE_FLTINV FE_INVALID /* invalid */ +#ifdef FPE_FLTSUB +#undef FPE_FLTSUB +#endif +#define FPE_FLTSUB (-2) /* N/A */ +#endif + +struct _hwx hwx_exception[] = { + { FPE_INTDIV, "integer divide by zero" }, + { FPE_INTOVF, "integer overflow" }, + { FPE_FLTDIV, "floating point divide by zero" }, + { FPE_FLTOVF, "floating point overflow" }, + { FPE_FLTUND, "floating point underflow" }, + { FPE_FLTRES, "floating point inexact result" }, + { FPE_FLTINV, "floating point invalid operation" }, + { FPE_FLTSUB, "subscript out of range" }, + { EOMAP, "" } +}; + + +/* ZXWHEN -- Post an exception handler or turn off interrupts. Return + * value of old handler, so that it may be restored by the user code if + * desired. The function EPA's are the type of value returned by ZLOCPR. + */ +int +ZXWHEN ( + XINT *sig_code, + XINT *epa, /* EPA of new exception handler */ + XINT *old_epa /* receives EPA of old handler */ +) +{ + static int first_call = 1; + int vex, uex; + SIGFUNC vvector; + + extern int kernel_panic (); + + + /* Convert code for virtual exception into an index into the table + * of exception handler EPA's. + */ + switch (*sig_code) { + case X_ACV: + case X_ARITH: + case X_INT: + case X_IPC: + vex = *sig_code - X_FIRST_EXCEPTION; + break; + default: + vex = (int) 0; + kernel_panic ("zxwhen: bad exception code"); + } + + *old_epa = handler_epa[vex]; + handler_epa[vex] = *epa; + vvector = (SIGFUNC) ex_handler; + + /* Check for attempt to post same handler twice. Do not return EPA + * of handler as old_epa as this could lead to recursion. + */ + if (*epa == (XINT) X_IGNORE) + vvector = (SIGFUNC) SIG_IGN; + else if (*epa == *old_epa) + *old_epa = (XINT) X_IGNORE; + + /* Set all hardware vectors in the indicated exception class. + * If interrupt (SIGINT) was disabled when we were spawned (i.e., + * when we were first called to set SIGINT) leave it that way, else + * we will get interrupted when the user interrupts the parent. + */ + for (uex=1; unix_exception[uex].x_vex != EOMAP; uex++) { + if (unix_exception[uex].x_vex == *sig_code) { + if (uex == SIGINT) { + if (first_call) { + if (setsig (uex, vvector) == (long) SIG_IGN) { + setsig (uex, SIG_IGN); + ignore_sigint++; + } + first_call = 0; + } else if (!ignore_sigint) { + if (debug_sig) + setsig (uex, SIG_DFL); + else + setsig (uex, vvector); + } + } else { + if (debug_sig) + setsig (uex, SIG_DFL); + else + setsig (uex, vvector); + } + } + } + + return (XOK); +} + + +/* SETSIG -- Post an exception handler for the given exception. + */ +static long +setsig (code, handler) +int code; +SIGFUNC handler; +{ + struct sigaction sig; + long status; + + sigemptyset (&sig.sa_mask); +#ifdef MACOSX + sig.sa_handler = (SIGFUNC) handler; +#else + sig.sa_sigaction = (SIGFUNC) handler; +#endif + sig.sa_flags = (SA_NODEFER|SA_SIGINFO); + status = (long) sigaction (code, &sig, NULL); + + return (status); +} + + +/* EX_HANDLER -- Called to handle an exception. Map OS exception into + * xwhen signal, call user exception handler. A default exception handler + * posted by the IRAF Main is called if the user has not posted another + * handler. If we get the software termination signal from the CL, + * stop process execution immediately (used to kill detached processes). + */ +#if (defined(MACOSX) && defined(OLD_MACOSX)) + +void +ex_handler (unix_signal, info, scp) +int unix_signal; +#ifdef OLD_MACOSX +void *info; +#else +siginfo_t *info; +#endif +#ifdef MACINTEL +ucontext_t *scp; +#else +struct sigcontext *scp; +#endif + +#else + +void +ex_handler ( + int unix_signal, + siginfo_t *info, + void *ucp +) +#endif +{ + XINT next_epa, epa, x_vex; + int vex; + +#ifndef LINUX64 + extern int sfpucw_(); +#endif + + last_os_exception = unix_signal; + last_os_hwcode = info ? info->si_code : 0; + + x_vex = unix_exception[unix_signal].x_vex; + vex = x_vex - X_FIRST_EXCEPTION; + epa = handler_epa[vex]; + + /* Reenable/initialize the exception handler. + */ + +#if defined(MACOSX) || defined(CYGWIN) + /* Clear the exception bits (ppc and x86). */ + feclearexcept (FE_ALL_EXCEPT); +#else +#ifdef LINUX + /* setfpucw (0x1372); */ + { +#ifdef MACUNIX + /* This is for Linux on a Mac, e.g., LinuxPPC (not MacOSX). */ + int fpucw = _FPU_IEEE; + + /* + if (unix_signal == SIGFPE) + kernel_panic ("unrecoverable floating exception"); + else + sfpucw_ (&fpucw); + if (unix_signal == SIGPIPE && !ignore_sigint) + sigset (SIGINT, (SIGFUNC) ex_handler); + */ + + sfpucw_ (&fpucw); +#else +#ifdef LINUX64 + /* + XINT fpucw = 0x336; + SFPUCW (&fpucw); + */ + fpu_control_t cw = + (_FPU_EXTENDED | _FPU_MASK_PM | _FPU_MASK_UM | _FPU_MASK_ZM | _FPU_MASK_DM); + _FPU_SETCW(cw); + +#else + int fpucw = 0x336; + sfpucw_ (&fpucw); +#endif +#endif + } +#endif +#endif + + +#ifdef SOLARIS + fpsetsticky (0x0); + fpsetmask (FP_X_INV | FP_X_OFL | FP_X_DZ); +#endif + + /* If signal was SIGINT, cancel any buffered standard output. */ + if (unix_signal == SIGINT) { + fcancel (stdout); + } + + /* Call user exception handler(s). Each handler returns with the + * "value" (epa) of the next handler, or X_IGNORE if exception handling + * is completed and processing is to continue normally. If the handler + * wishes to restart the process, i.e., initiate error recovery, then + * the handler procedure will not return. + */ + for (next_epa=epa; next_epa != (XINT) X_IGNORE; + ((SIGFUNC)epa)(&x_vex,&next_epa)) + epa = next_epa; +} + + +/* ZXGMES -- Get the machine dependent integer code and error message for the + * most recent exception. The integer code XOK is returned if no exception + * has occurred, or if we are called more than once. + */ +int +ZXGMES ( + XINT *os_exception, + PKCHAR *errmsg, + XINT *maxch +) +{ + register int v; + char *os_errmsg; + + *os_exception = last_os_exception; + + if (last_os_exception == XOK) + os_errmsg = ""; + else { + os_errmsg = unix_exception[last_os_exception].x_name; + if (last_os_exception == SIGFPE) { + for (v=0; hwx_exception[v].v_code != EOMAP; v++) + if (hwx_exception[v].v_code == last_os_hwcode) { + os_errmsg = hwx_exception[v].v_msg; + break; + } + } + } + + strncpy ((char *)errmsg, os_errmsg, (int)*maxch); + ((char *)errmsg)[*maxch] = EOS; + + last_os_exception = XOK; + + return (XOK); +} + + +#ifdef LINUX64 + +int +gfpucw_ (XINT *xcw) +{ + fpu_control_t cw; + _FPU_GETCW(cw); + *xcw = cw; + return cw; +} + +int +sfpucw_ (XINT *xcw) +{ + fpu_control_t cw = *xcw; + _FPU_SETCW(cw); + return cw; +} + +#endif diff --git a/unix/os/zzdbg.c b/unix/os/zzdbg.c new file mode 100644 index 00000000..eaffd8ad --- /dev/null +++ b/unix/os/zzdbg.c @@ -0,0 +1,158 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include + +#define import_spp +#define import_kernel +#define import_knames +#define import_xnames +#define import_prtype +#include + + + +void zzval_ (XINT *val) +{ + fprintf (stderr, "zzprnt: %ld 0x%lx\n", (long)*val, (long)*val); +} + + +void zzprnt_ (XINT *val, XINT *len) +{ + int i; + + + fprintf (stderr, "zzprnt:\n"); + for (i=0; i < *len; i++) + fprintf (stderr, "%ld\n", (long)*val); + fprintf (stderr, "\n"); +} + + +void zzmsg_ (XCHAR *buf, XINT *val) +{ + char i, *cp = (char *) buf; + + fprintf (stderr, "zzmsg: "); + for (i=0; i < 64; i++, cp++) { + if (*cp == '\n' || (*cp == '\0' && *(cp+1) == '\0')) + break; + fprintf (stderr, "%c", *cp); + } + fprintf (stderr, " %ld 0x%lx\n", (long)*val, (long)*val); + fflush (stderr); +} + + +void zzmfd_ (XCHAR *buf, XINT *fd, XINT *val) +{ + char i, *cp = (char *) buf; + + fprintf (stderr, "zzmfd[%2d]: ", (int) *fd); + for (i=0; i < 64; i++) { + if (*cp == '\n' || (*cp == '\0' && *(cp+1) == '\0')) + break; + fprintf (stderr, "%c", *cp++); + } + fprintf (stderr, " %ld 0x%lx\n", (long)*val, (long)*val); + fflush (stderr); +} + + +void zzmstr_ (XCHAR *s1, XCHAR *s2, XINT *val) { + + char i, *c1 = (char *) s1, *c2 = (char *) s2; + + fprintf (stderr, "zzmstr: "); + for (i=0; i < 64; i++) { + if (*c1 == '\n' || (*c1 == '\0' && *(c1+1) == '\0')) + break; + fprintf (stderr, "%c", *c1++); + } + fprintf (stderr, " "); + for (i=0; i < 64; i++) { + if (*c2 == '\n' || (*c2 == '\0' && *(c2+1) == '\0')) + break; + fprintf (stderr, "%c", *c2++); + } + fprintf (stderr, " %ld 0x%lx\n", (long)*val, (long)*val); + fflush (stderr); +} + + +void zzdmp_ (XCHAR *buf, XINT *len) { + int i; + char *cp = (char *) buf; + for (i=0; i < *len; i++, cp++) { + if (*cp == '\0' && *(cp+1) == '\0') + break; + else + fprintf (stderr, "%c'", *cp); + } + fprintf (stderr, "\n"); + fflush (stderr); +} + + +void mdump_ (XINT *buf, XINT *nbytes) +{ + register int i=0, j=0, nb=(*nbytes); + char ch, *p = LOC_TO_ADDR(*buf,char); + + /* + fprintf (stderr, "*buf = %d %d %d %d %d\n", + *buf, ((int)p)*2, (((int)p)*4-4), ((int)(*buf))*2, (((int)(*buf))*4-4) ); + p = ((*buf) * 4 - 4); + */ + + printf ("\n"); + while ( i < nb ) { + printf ("%4d %ld 0x%lx\t", i, (long)(p+i), (long)(p+i) ); + for (j=0; j < 8; j++) { + ch = *(p+i); + printf ("0x%02x ", (ch & 0xff)); + i++; + } + printf ("\n"); + } + printf ("\n"); +} + + +void +zzpeek_ (void *a, XINT *nelems, XINT *nl) +{ + XINT i; + char *c; + + for (i=0, c=(char *)a; i < *nelems; i++) + printf ("%2d ", *c++); + printf ("%s", (*nl ? " : " : "\n")); +} + + +void +zzpdat_ (XCHAR *msg, void *a, XINT *nelems, XINT *nl) +{ + XINT i; + char *c; + + for (i=0; i < 32; i++) { + if (msg[i]) + printf ("%c", (char )msg[i]); + else { + printf (": "); + break; + } + } + + for (i=0, c=(char *)a; i < *nelems; i++) + printf ("%2d ", *c++); + printf ("%s", (*nl ? " : " : "\n")); +} diff --git a/unix/os/zzepro.c b/unix/os/zzepro.c new file mode 100644 index 00000000..9f046716 --- /dev/null +++ b/unix/os/zzepro.c @@ -0,0 +1,84 @@ +#include +#include +#ifdef MACOSX +#include +#include +#endif +#ifdef CYGWIN +#include +#include +#endif + +#define import_spp +#define import_knames +#include + + + +#if (defined(MACOSX) && defined(OLD_MACOSX)) +void ex_handler ( int, int, struct sigcontext * ); +#else +void ex_handler ( int, siginfo_t *, void * ); +#endif + + +/* + * ZZEPRO.C -- Code which is executed at the end of every procedure. + */ + +/* NOTE: Following is also picked up by Mac/Intel. */ +#if ( (defined(MACOSX) || defined(CYGWIN)) && !defined(IPAD)) + +int macosx_sigmask = (FE_DIVBYZERO|FE_OVERFLOW|FE_INVALID); + +void mxmask_ (void); +void mxumsk_ (void); + + +/* ZZEPRO.C -- On MacOSX (which under 10.1.x can't raise a hardware + * exception) we check at the end of every procedure to see if a floating + * exception occurred. + */ +int +ZZEPRO (void) +{ + fexcept_t flagp; + + fegetexceptflag (&flagp, macosx_sigmask); + if (flagp & macosx_sigmask) { + siginfo_t info; + info.si_code = (flagp & macosx_sigmask); + ex_handler (SIGFPE, &info, NULL); + } + + /* Clear the exception. */ + flagp = (fexcept_t) 0; + feclearexcept (FE_ALL_EXCEPT); + + return (XOK); +} + +/* Mask or unmask the invalid operand exception. Invalid must be + * masked to be able to operate upon invalid operands, e.g., to filter + * out NaN/Inf in IEEE i/o code (see as$ieee.gx). + */ +void mxmask_ (void) +{ + macosx_sigmask &= ~FE_INVALID; +} + +void mxumsk_ (void) +{ + fexcept_t flagp; + + fegetexceptflag (&flagp, macosx_sigmask); + macosx_sigmask |= FE_INVALID; + flagp &= ~FE_INVALID; + + fesetexceptflag (&flagp, macosx_sigmask); +} + + +#else +int ZZEPRO ( void) { return (XOK); } +#endif diff --git a/unix/os/zzexit.c b/unix/os/zzexit.c new file mode 100644 index 00000000..a54cfc38 --- /dev/null +++ b/unix/os/zzexit.c @@ -0,0 +1,17 @@ +#include + +#define import_spp +#include + +/* + * ZZEXIT.C -- Fortran callable exit procedure. Some systems (e.g. libf2c) + * require this procedure. We implement it as a separate library procedure + * so that it can be replaced by a user exit procedure. + */ +int +exit_ (code) +XINT *code; +{ + exit (*code); + return (XOK); +} diff --git a/unix/os/zzpstr.c b/unix/os/zzpstr.c new file mode 100644 index 00000000..30648b65 --- /dev/null +++ b/unix/os/zzpstr.c @@ -0,0 +1,176 @@ +#include +#include +#include +#include + +#define import_spp +#include + +/* + * ZZPSTR.C -- Support for debugging SPP programs. + * + * zzpstr (s1, s2) # Write a debug message to the process stderr + * zzlstr (s1, s2) # Write a debug message to /tmp/k.log + * spp_printstr (s) # GDB support function + * spp_printmemc (memc_p) # GDB support function + * + * The procedures zzpstr and zzlstr are meant to be called from within + * compiled SPP code to write debug messages to either the process stderr + * or to a log file. This is different than writing to SPP STDERR since + * the latter is a pseudofile (it gets sent to the CL before being written + * out). In other words zzpstr/zzlstr are low level debug functions, + * comparable to a host fprintf. + * + * spp_printstr and spp_printmemc are called from a debugger (GDB) to + * print char strings. spp_printstr prints a char variable as an EOS + * terminated string. spp_printmemc does the same thing, but takes a Memc + * pointer variable as input. + * + * The following commands can be added to your .gdbinit file to make it + * easier to use these functions: + * + * define ps + * call spp_printstr ($arg0) + * end + * + * define pc + * call spp_printmemc ($arg0) + * end + * + * Then you can type e.g., "ps fname" to print char variable fname, + * or "pc ip" to print the string pointed to by SPP Memc pointer "ip". + * Both of these functions will print tabs and newlines as \t and \n, + * and other control codes as \ooo where ooo is the octal value of the + * character. + */ + +#define LOGFILE "/tmp/k.log" + +void spp_printmemc (long memc_ptr); +void spp_printstr (XCHAR *s); + + + +/* SPP_DEBUG -- Dummy function called to link the SPP debug functions into + * a program. + */ +int spp_debug (void) { return (0); } + + +/* ZZPSTR -- Write SPP text data directly to the host stderr. Up to two + * strings may be ouptut. Either may be the null pointer to disable. + * A newline is added at the end if not present in the last string. + */ +int +zzpstr_ (XCHAR *s1, XCHAR *s2) +{ + register XCHAR *s, *ip; + register char *op; + char buf[4096]; + int lastch = 0; + + + if ( (s = s1) ) { + for (ip=s, op=buf; (*op = *ip++); op++) + ; + lastch = *(op-1); + write (2, buf, op-buf); + } + + if ( (s = s2) ) { + for (ip=s, op=buf; (*op = *ip++); op++) + ; + lastch = *(op-1); + write (2, buf, op-buf); + } + + if (lastch != '\n') + write (2, "\n", 1); + + return (XOK); +} + + +/* ZZLSTR -- Write SPP text data to a log file. + */ +int +zzlstr_ (XCHAR *s1, XCHAR *s2) +{ + register XCHAR *s, *ip; + register char *op; + char buf[4096]; + int lastch = 0; + int status = 0, fd; + + if ((fd = open (LOGFILE, O_CREAT|O_WRONLY|O_APPEND, 0644)) < 0) + return (fd); + + if ( (s = s1) ) { + for (ip=s, op=buf; (*op = *ip++); op++) + ; + lastch = *(op-1); + status = write (fd, buf, op-buf); + } + + if ( (s = s2) ) { + for (ip=s, op=buf; (*op = *ip++); op++) + ; + lastch = *(op-1); + status = write (fd, buf, op-buf); + } + + if (lastch != '\n') + status = write (fd, "\n", 1); + + status = close (fd); + return (status); +} + + +/* SPP_PRINTSTR -- GDB callable debug function to print an EOS terminated SPP + * string passed as a char array. + */ +void +spp_printstr (XCHAR *s) +{ + register XCHAR *ip; + register char *op, *otop; + static char obuf[1024]; + int ch; + + for (ip=s+1, op=obuf, otop=obuf+1020; (ch = *ip); ip++) { + if (!isprint (ch)) { + if (ch == '\t') { + *op++ = '\\'; + *op++ = 't'; + } else if (ch == '\n') { + *op++ = '\\'; + *op++ = 'n'; + } else { + *op++ = '\\'; + *op++ = ((ch >> 6) & 07) + '0'; + *op++ = ((ch >> 3) & 07) + '0'; + *op++ = ( ch & 07) + '0'; + } + } else + *op++ = ch; + + if (op >= otop) + break; + } + + *op++ = '\0'; + printf ("%s\n", obuf); + fflush (stdout); +} + + +/* SPP_PRINTMEMC -- GDB callable debug function to print an EOS terminated SPP + * string passed as a pointer to char. + */ +void +spp_printmemc (long memc_ptr) +{ + XCHAR *str = (XCHAR *) ((memc_ptr - 1) * 2 - 2); + spp_printstr (str); +} diff --git a/unix/os/zzsetk.c b/unix/os/zzsetk.c new file mode 100644 index 00000000..d1b374cc --- /dev/null +++ b/unix/os/zzsetk.c @@ -0,0 +1,38 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include + +#define import_spp +#define import_knames +#include + +extern char os_process_name[]; +extern PKCHAR osfn_bkgfile[]; +extern int save_prtype; +extern int ipc_isatty; +extern int ipc_in, ipc_out; + +/* ZZSETK -- Internal kernel routine, used by the zmain to set the values + * of certain internal kernel parameters. + */ +int +ZZSETK ( + char *ospn, + char *osbfn, + int prtype, + int isatty, + int in, + int out +) +{ + strcpy (os_process_name, ospn); + strcpy ((char *)osfn_bkgfile, osbfn); + save_prtype = prtype; + ipc_isatty = isatty; + ipc_in = in; + ipc_out = out; + + return (XOK); +} diff --git a/unix/os/zzstrt.c b/unix/os/zzstrt.c new file mode 100644 index 00000000..32138e09 --- /dev/null +++ b/unix/os/zzstrt.c @@ -0,0 +1,628 @@ +/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc. + */ + +#include +#include +#include +#include +#include +#include + +#ifdef CYGWIN +# include +#else +#ifdef LINUX +# include +# undef SOLARIS +#endif +#endif + +#ifdef SHLIB +#ifdef SOLARIS +#include +#include +#include +#include +#include +#else +#include +#include +#endif +#endif + +#ifdef sun +#include +#endif + +#ifdef SOLARIS +#include +#endif + +#ifdef LINUXPPC +#define MACUNIX +#endif + +#ifdef MACOSX +#include +#include +#ifndef MACINTEL +#define MACUNIX +#endif +#endif + +#define import_spp +#define import_kernel +#define import_knames +#define import_xnames +#define import_prtype +#include + +/* + * ZZSTRT,ZZSTOP -- Routines to perform initialization and cleanup functions + * during process startup and shutdown, when the IRAF kernel routines are being + * called from a program which does not have a ZMAIN. + */ + +/* #define DEBUG */ + +static int prtype, ipc_isatty=NO; +static int ipc_in = 0, ipc_out = 0; +static char os_process_name[SZ_FNAME]; +static char osfn_bkgfile[SZ_PATHNAME]; +extern int errno; + +#ifdef SHLIB +extern char *((*environ)[]); + +extern int sh_debug; /* map shared image writeable */ +static short debug_ieee = 0; +extern unsigned USHLIB[], VSHLIB[]; /* shared library descriptors */ +static unsigned vshlib[8]; +#define v_version vshlib[0] /* shared image version number */ +#define v_base vshlib[1] /* exported shimage addresses */ +#define v_etext vshlib[2] +#define v_edata vshlib[3] +#define v_end vshlib[4] +#define u_version USHLIB[0] /* application version number */ +#define sh_machtype USHLIB[6] /* machine architecture */ +#endif + +#define align(a) ((a)&(~pmask)) + +#ifdef i386 +/* The following kludge is required due to a 386i linker error to prevent + * a BSS size of 0 for small processes with a short BSS segment. If BSS is + * set to zero in the file header but some BSS storage is required, the image + * will die on a segmentation violation during startup while trying to + * initialize the value of "environ". + */ +int BSS_kludge[256]; +#endif + +void ready_ (void); + +extern int ZAWSET(), ZOPNTY(), ZZSETK(); + + + +/* ZZSTRT -- Initialize the IRAF kernel at process startup time. + */ +int +ZZSTRT (void) +{ + XINT wsetsize=0L, junk; +#ifdef SHLIB + static int fd = 0; + struct stat fi; + char *segname; + XCHAR *bp; +#endif +#ifndef LINUX64 + extern void sfpucw_(); +#endif + extern int spp_debug(); + + + spp_debug (); + + /* Initialize globals. + */ + sprintf (os_process_name, "%d", getpid()); + strcpy (osfn_bkgfile, ""); + prtype = PR_HOST; + + /* Initialize the kernel file descriptor. */ + zfd[0].fp = stdin; zfd[0].flags = KF_NOSEEK; + zfd[1].fp = stdout; zfd[1].flags = KF_NOSEEK; + zfd[2].fp = stderr; zfd[2].flags = KF_NOSEEK; + +#ifdef SHLIB + /* Map in the Sun/IRAF shared library, if the calling process was + * linked with the shared library, and the shared library has not + * already been mapped (fd != 0). (See unix/shlib for more info). + * (This is rather monolithic and should probably be isolated to a + * separate module, but we are not trying for a general solution here). + */ + if (USHLIB[0] && (!fd || (fd && fstat(fd,&fi) == -1))) { + register unsigned pgsize, pmask; + unsigned t_off, t_len; + unsigned d_off, d_len; + unsigned b_off, b_len; + unsigned b_start, b_bytes; + static char envdef[SZ_FNAME]; + char shimage[SZ_FNAME]; + char *shlib, *arch; + extern char *getenv(); + caddr_t addr; + unsigned hsize; +#ifdef SOLARIS + register Elf32_Phdr *phdr; + register Elf32_Ehdr *ehdr; + caddr_t t_loc, d_loc, b_loc; + int adjust, phnum, nseg, i; + struct utsname uts; + Elf32_Phdr *phdr_array; + Elf32_Phdr seg[32]; + Elf *elf; +#else + unsigned t_loc, d_loc, b_loc; +#endif + + /* Determine the architecture of the shared library. */ + switch (sh_machtype) { + case 1: /* see shlib/mkshlib.csh */ + arch = "sparc"; break; + case 2: + arch = "i386"; break; + case 3: + arch = "f68881"; break; + case 4: + arch = "ffpa"; break; + case 5: + arch = "ssun"; break; + case 6: + arch = "sf2c"; break; + default: + arch = "fsoft"; break; + } + + /* Define IRAFARCH if not already defined in the process + * environment or if the definition does not match the architecture + * of the executable being run. This is necessary for irafpath(), + * below, to successfully find the shared image. + */ + sprintf (envdef, "IRAFARCH=%s", arch); + if (!(arch = getenv("IRAFARCH")) || strcmp(envdef,arch)) + putenv (envdef); + +#ifdef SOLARIS + /* Open the shared library file. In the case of Solaris the + * statically linked shared library doesn't work for both Solaris + * 2.3 and 2.4, and a separate shared library is required for + * each. Call uname() to get the OS version and use the + * appropriate shared library. If this isn't found attempt to + * fallback on the generic version. + */ + uname (&uts); + sprintf (shimage, "S%d_%s.e", u_version, uts.release); + shlib = irafpath (shimage); + if (shlib == NULL || (fd = open (shlib, 0)) == -1) { + sprintf (shimage, "S%d.e", u_version); + shlib = irafpath (shimage); + } + if (shlib == NULL || (fd = open (shlib, 0)) == -1) { + fprintf (stderr, + "Error: cannot open iraf shared library %s\n", shlib); + exit (1); + } +#else + /* Open the shared library file */ + sprintf (shimage, "S%d.e", u_version); + shlib = irafpath (shimage); + if (shlib == NULL || (fd = open (shlib, 0)) == -1) { + fprintf (stderr, + "Error: cannot open iraf shared library %s\n", shlib); + exit (1); + } +#endif + +#ifdef SOLARIS + /* With Solaris executables are ELF format files. The file + * and program headers tell where everything is and how to map + * the image segments. + */ + elf_version (EV_CURRENT); + elf = elf_begin (fd, ELF_C_READ, NULL); + if (!elf) { + fprintf (stderr, "%s: not an ELF format file\n", shlib); + exit (2); + } + if (!(ehdr = elf32_getehdr (elf))) { + fprintf (stderr, "%s: cannot read file header\n", shlib); + exit (1); + } + if ((phnum = ehdr->e_phnum) <= 0 || + !(phdr_array = elf32_getphdr (elf))) { + fprintf (stderr, "%s: cannot read program header table\n", + shlib); + exit (1); + } + + /* Get a list of the loadable segments. */ + for (i=0, nseg=0; i < phnum; i++) { + phdr = (Elf32_Phdr *)((char *)phdr_array + i*ehdr->e_phentsize); + if (phdr->p_type == PT_LOAD) + seg[nseg++] = *phdr; + } + + /* Read in the vshlib array, which is stored in the text segment + * of the shared image. + */ + if (nseg) { + phdr = &seg[0]; + hsize = (unsigned)((char *)VSHLIB) - USHLIB[1]; + /* lseek (fd, phdr->p_offset + (long)hsize, 0); */ + lseek (fd, (off_t)hsize, 0); + if (read (fd, (char *)vshlib, sizeof(vshlib)) != + sizeof(vshlib)) { + fprintf (stderr, "Read error on %s\n", shlib); + exit (1); + } + } else { + fprintf (stderr, + "Error: cannot open iraf shared library %s\n", shlib); + exit (1); + } + + pgsize = sysconf (_SC_PAGESIZE); + pmask = pgsize - 1; + + /* Determine the file and memory offsets of each segment of the + * shared image. + */ + phdr = &seg[0]; + adjust = phdr->p_offset % pgsize; + + t_off = phdr->p_offset - adjust; + t_loc = (caddr_t) ((int)phdr->p_vaddr - adjust); + t_len = phdr->p_filesz + adjust; + + phdr = &seg[1]; + adjust = phdr->p_offset % pgsize; + + d_off = phdr->p_offset - adjust; + d_loc = (caddr_t) ((int)phdr->p_vaddr - adjust); + d_len = phdr->p_filesz + adjust; + + /* Map the BSS segment beginning with the first hardware page + * following the end of the data segment. + */ + b_off = 0; /* anywhere will do */ + b_loc = (caddr_t) align ((int)d_loc + d_len + pgsize); + b_len = phdr->p_vaddr + phdr->p_memsz - (int)b_loc; + + b_start = phdr->p_vaddr + phdr->p_filesz; + b_bytes = phdr->p_memsz - phdr->p_filesz; + +#else !SOLARIS + /* Compute the location and size of each segment of the shared + * image memory. The shared image is mapped at address s_base. + */ + hsize = (unsigned)((char *)VSHLIB) - USHLIB[1]; + lseek (fd, (off_t)hsize, 0); + if (read (fd, (char *)vshlib, sizeof(vshlib)) != sizeof(vshlib)) { + fprintf (stderr, "Read error on %s\n", shlib); + exit (1); + } + +#ifdef i386 + /* Map the shared image on a Sun-386i (SysV COFF format). + */ + pgsize = getpagesize(); + pmask = pgsize - 1; + + /* Determine the file and memory offsets of each segment of the + * shared image. + */ + + t_off = 0; /* file offset */ + t_loc = v_base; /* location in memory */ + t_len = v_etext - v_base; /* segment length */ + + d_off = align (v_etext) - v_base; /* map file page twice */ + d_loc = align (v_etext); + d_len = v_edata - d_loc; + + b_off = 0; /* anywhere will do */ + b_loc = align (d_loc + d_len + pmask); + b_len = v_end - b_loc; + + b_start = v_edata; + b_bytes = v_end - v_edata; +#else + /* Map the shared image on a Sun-3 or Sun-4. + */ + pgsize = PAGSIZ; + pmask = pgsize - 1; + + /* Determine the file and memory offsets of each segment of the + * shared image. We cannot use the macros since the + * text segment does not begin at the default location. Also, + * the size of the BSS segment in the file header is not correct + * (under SunOS 4.0), so we compute directly from _end. + */ + + t_off = 0; /* file offset */ + t_loc = v_base; /* location in memory */ + t_len = v_etext - v_base; /* segment length */ + + d_off = align (t_len + pmask); + d_loc = (v_etext + SEGSIZ-1) / SEGSIZ * SEGSIZ; + d_len = v_edata - d_loc; + + /* Map the BSS segment beginning with the first hardware page + * following the end of the data segment. This need not be + * the same as the PAGSIZ used for a.out. v_edata-1 is the + * address of the last byte of the data segment. + */ + b_off = 0; /* anywhere will do */ + b_loc = ((v_edata-1) & ~(getpagesize()-1)) + getpagesize(); + b_len = v_end - b_loc; + + b_start = v_edata; + b_bytes = v_end - v_edata; +#endif i386 +#endif SOLARIS + +#ifdef DEBUG + fprintf (stderr, " text: %8x %8x %8x -> %8x etext = %8x\n", + t_loc, t_len, t_off, t_loc + t_len, v_etext); + fprintf (stderr, " data: %8x %8x %8x -> %8x edata = %8x\n", + d_loc, d_len, d_off, d_loc + d_len, v_edata); + fprintf (stderr, " bss: %8x %8x %8x -> %8x end = %8x\n", + b_loc, b_len, b_off, b_loc + b_len, v_end); + fprintf (stderr, " zero: %8x %8x %8s -> %8x\n", + b_start, b_bytes, " ", b_start + b_bytes); +#endif DEBUG + + /* Map the header region of the "text" segment read-write. + * This area contains any commons exported by the shared image. + */ + addr = mmap (t_loc, hsize, PROT_READ|PROT_WRITE, + MAP_PRIVATE|MAP_FIXED, fd, t_off); + if ((int)addr == -1) { + segname = "header"; + goto maperr; + } + + /* Map the text segment read-only shared, unless the sh_debug + * flag is set (-w command line option), in which case the shared + * text is mapped private so that it may be modified, e.g., to + * set breakpoints while debugging a process. + */ + addr = mmap (t_loc+hsize, t_len-hsize, PROT_READ|PROT_EXEC, + (sh_debug?MAP_PRIVATE:MAP_SHARED)|MAP_FIXED, fd, t_off+hsize); + if ((int)addr == -1) { + segname = "text"; + goto maperr; + } + + /* Map the data segment read-write. */ + addr = mmap (d_loc, d_len, PROT_READ|PROT_WRITE|PROT_EXEC, + MAP_PRIVATE|MAP_FIXED, fd, d_off); + if ((int)addr == -1) { + segname = "data"; + goto maperr; + } + + /* The BSS section has to be initialized to zero. We can map this + * onto any convenient file data provided we map it private and + * promptly modify (zero) the pages. We assume here that the size + * of the BSS segment does not exceed the file size; this would + * not be true in general but should always be true in our case. + */ + addr = mmap (b_loc, b_len, PROT_READ|PROT_WRITE|PROT_EXEC, + MAP_PRIVATE|MAP_FIXED, fd, b_off); + + if ((int)addr == -1) { + segname = "bss"; +maperr: fprintf (stderr, "Error: cannot map the iraf shared library"); + fprintf (stderr, ", seg=%s, errno=%d\n", segname, errno); + exit (2); + } + + /* Zero the bss segment. */ + bzero (b_start, b_bytes); + + /* Verify that the version number and base address match. */ + if (USHLIB[0] != VSHLIB[0] || USHLIB[1] != VSHLIB[1]) { + fprintf (stderr, + "Error: iraf shared library mismatch, please relink\n"); + exit (3); } + + /* Link the memory allocator function in the main process to stubs + * in the shared library, so that the library routines will + * allocate memory in the data space of the client. + */ +#ifdef SOLARIS + VLIBINIT (environ, malloc, realloc, free, + dlopen, dlclose, dlsym, dlerror); +#else + VLIBINIT (environ, malloc, realloc, free); +#endif + } +#endif /* SHLIB */ + + /* Dummy routine called to indicate that mapping is complete. */ + ready_(); + +#if defined(MACOSX) || defined(CYGWIN) + /* Clears the exception-occurred bits in the FP status register. + */ + feclearexcept (FE_ALL_EXCEPT); +#else + +#if defined(LINUX) + /* Enable the common IEEE exceptions. Newer Linux systems disable + * these by default, the usual SYSV behavior. + */ + + /* Old code; replaced by SFPUCW in as$zsvjmp.s + asm ("fclex"); + setfpucw (0x1372); + */ + { + /* 0x332: round to nearest, 64 bit precision, mask P-U-D. */ +#ifdef MACUNIX + int fpucw = _FPU_IEEE; +#else + int fpucw = 0x332; +#endif +#ifdef LINUX64 + /* + XINT fpucw = 0x332; + SFPUCW (&fpucw); + */ + fpu_control_t cw = + (_FPU_EXTENDED | _FPU_MASK_PM | _FPU_MASK_UM | _FPU_MASK_DM); + _FPU_SETCW(cw); +#else + sfpucw_ (&fpucw); +#endif + } +#endif +#endif + +#ifdef SOLARIS + /* Enable the common IEEE exceptions. _ieee_enbint is as$enbint.s. + */ +#ifdef X86 + fpsetsticky (0x0); + fpsetmask (FP_X_INV | FP_X_OFL | FP_X_DZ); +#else + _ieee_enbint ( + (1 << (int)fp_division) | + (1 << (int)fp_overflow) | + (1 << (int)fp_invalid) + ); +#endif + +#else +#ifdef SUNOS + /* The following enables the common IEEE floating point exceptions + * invalid, overflow, and divzero, causing the program to abort if + * any of these are detected. If ZZSTRT is called from an IRAF + * program the abort action will normally be overidden when the IRAF + * main posts it's own handler for X_ARITH class exceptions. + */ + ieee_handler ("set", "common", SIGFPE_ABORT); + + /* The following disables recomputation of subnormal results or + * operands, which is done in software with an exception handler + * for machines with Weitek hardware, hence is very slow. This + * is a deviation from the IEEE standard, but is consistent with + * the behavior of most non-IEEE hardware, and well designed + * software should not generate any subnormal values in any case, + * let alone depend upon small deviations in the value of such + * subnormals. + */ + abrupt_underflow_(); + + /* The bitflag variable debug_ieee may be set nonzero to modify + * the default behavior (rounding direction and precision) of + * the IEEE hardware. + */ +# define FP_NEAREST 0001 /* round toward nearest */ +# define FP_TOZERO 0002 /* round toward zero */ +# define FP_NEGATIVE 0004 /* round toward negative infinity */ +# define FP_POSITIVE 0010 /* round toward positive infinity */ +# define FP_EXTENDED 0020 /* round to extended precision */ +# define FP_DOUBLE 0040 /* round to ieee double precision */ +# define FP_SINGLE 0100 /* round to ieee single precision */ + + if (debug_ieee) { + char *set = "set"; + char *direction = "direction"; + char *precision = "precision"; + + /* Set the rounding direction mode. */ + if (debug_ieee & FP_NEAREST) + ieee_flags (set, direction, "nearest", NULL); + if (debug_ieee & FP_TOZERO) + ieee_flags (set, direction, "tozero", NULL); + if (debug_ieee & FP_NEGATIVE) + ieee_flags (set, direction, "negative", NULL); + if (debug_ieee & FP_POSITIVE) + ieee_flags (set, direction, "positive", NULL); + + /* Set the rounding precision mode. */ + if (debug_ieee & FP_EXTENDED) + ieee_flags (set, precision, "extended", NULL); + if (debug_ieee & FP_DOUBLE) + ieee_flags (set, precision, "double", NULL); + if (debug_ieee & FP_SINGLE) + ieee_flags (set, precision, "single", NULL); + } +#else +#ifdef mc68000 + /* Enable the IEEE floating point exceptions, for old versions of + * SunOS. Pretty much obsolete now... + */ +# define FP_INEXACT 0000010 +# define FP_DIVIDE 0000020 +# define FP_UNDERFLOW 0000040 +# define FP_OVERFLOW 0000100 +# define FP_INVALID 0000200 +# define FP_INEX1 0000400 +# define FP_INEX2 0001000 +# define FP_DZ 0002000 +# define FP_UNFL 0004000 +# define FP_OVFL 0010000 +# define FP_OPERR 0020000 +# define FP_SNAN 0040000 +# define FP_BSUN 0100000 + { + int mode = FP_BSUN|FP_SNAN|FP_OPERR|FP_DZ|FP_OVFL|FP_INVALID; + fpmode_ (&mode); + } +#endif +#endif +#endif + +#ifdef SYSV + /* Initialize the time zone data structures. */ + tzset(); +#endif + + /* Place a query call to ZAWSET to set the process working set limit + * to the IRAF default value, in case we did not inherit a working set + * limit value from the parent process. + */ + ZAWSET (&wsetsize, &junk, &junk, &junk); + + /* Initialize the stdio streams. */ + { XINT ro = READ_ONLY, wo = WRITE_ONLY, chan; + + ZOPNTY ((PKCHAR *)U_STDIN, &ro, &chan); + ZOPNTY ((PKCHAR *)U_STDOUT, &wo, &chan); + ZOPNTY ((PKCHAR *)U_STDERR, &wo, &chan); + } + + /* Pass the values of the kernel parameters into the kernel. */ + ZZSETK (os_process_name, osfn_bkgfile, prtype, ipc_isatty, + &ipc_in, &ipc_out); + + return (XOK); +} + + +/* ZZSTOP -- Clean up prior to process shutdown. + */ +int ZZSTOP (void) { return (XOK); } + + +/* ready -- This is a dummy routine used when debugging to allow a breakpoint + * to be set at a convenient point after the shared image has been mapped in. + */ +void ready_ (void) {} + -- cgit