aboutsummaryrefslogtreecommitdiff
path: root/unix/os
diff options
context:
space:
mode:
Diffstat (limited to 'unix/os')
-rw-r--r--unix/os/README7
-rw-r--r--unix/os/alloc.c273
-rw-r--r--unix/os/dio.c9
-rw-r--r--unix/os/doc/Mach.notes32
-rw-r--r--unix/os/doc/os.hd71
-rw-r--r--unix/os/doc/os.ms4249
-rw-r--r--unix/os/doc/ostoc.ms130
-rw-r--r--unix/os/doc/zalocd.hlp53
-rw-r--r--unix/os/doc/zardbf.hlp56
-rw-r--r--unix/os/doc/zawrbf.hlp56
-rw-r--r--unix/os/doc/zawset.hlp42
-rw-r--r--unix/os/doc/zawtbf.hlp34
-rw-r--r--unix/os/doc/zcall.hlp39
-rw-r--r--unix/os/doc/zclcpr.hlp33
-rw-r--r--unix/os/doc/zcldir.hlp28
-rw-r--r--unix/os/doc/zcldpr.hlp38
-rw-r--r--unix/os/doc/zclsbf.hlp32
-rw-r--r--unix/os/doc/zclstx.hlp35
-rw-r--r--unix/os/doc/zfacss.hlp37
-rw-r--r--unix/os/doc/zfaloc.hlp34
-rw-r--r--unix/os/doc/zfchdr.hlp29
-rw-r--r--unix/os/doc/zfdele.hlp29
-rw-r--r--unix/os/doc/zfgcwd.hlp26
-rw-r--r--unix/os/doc/zfinfo.hlp66
-rw-r--r--unix/os/doc/zfiobf.hlp53
-rw-r--r--unix/os/doc/zfiolp.hlp54
-rw-r--r--unix/os/doc/zfiomt.hlp65
-rw-r--r--unix/os/doc/zfiopr.hlp58
-rw-r--r--unix/os/doc/zfiosf.hlp51
-rw-r--r--unix/os/doc/zfiotx.hlp44
-rw-r--r--unix/os/doc/zfioty.hlp75
-rw-r--r--unix/os/doc/zflstx.hlp33
-rw-r--r--unix/os/doc/zfmkcp.hlp40
-rw-r--r--unix/os/doc/zfpath.hlp32
-rw-r--r--unix/os/doc/zfprot.hlp47
-rw-r--r--unix/os/doc/zfrnam.hlp40
-rw-r--r--unix/os/doc/zfsubd.hlp76
-rw-r--r--unix/os/doc/zfxdir.hlp31
-rw-r--r--unix/os/doc/zgettx.hlp57
-rw-r--r--unix/os/doc/zgfdir.hlp37
-rw-r--r--unix/os/doc/zgtime.hlp28
-rw-r--r--unix/os/doc/zgtpid.hlp25
-rw-r--r--unix/os/doc/zintpr.hlp34
-rw-r--r--unix/os/doc/zlocpr.hlp35
-rw-r--r--unix/os/doc/zlocva.hlp47
-rw-r--r--unix/os/doc/zmain.hlp62
-rw-r--r--unix/os/doc/zmaloc.hlp71
-rw-r--r--unix/os/doc/zmfree.hlp36
-rw-r--r--unix/os/doc/znottx.hlp45
-rw-r--r--unix/os/doc/zopcpr.hlp33
-rw-r--r--unix/os/doc/zopdir.hlp34
-rw-r--r--unix/os/doc/zopdpr.hlp37
-rw-r--r--unix/os/doc/zopnbf.hlp53
-rw-r--r--unix/os/doc/zopntx.hlp55
-rw-r--r--unix/os/doc/zoscmd.hlp36
-rw-r--r--unix/os/doc/zpanic.hlp32
-rw-r--r--unix/os/doc/zputtx.hlp59
-rw-r--r--unix/os/doc/zraloc.hlp45
-rw-r--r--unix/os/doc/zsektx.hlp43
-rw-r--r--unix/os/doc/zsttbf.hlp53
-rw-r--r--unix/os/doc/zstttx.hlp50
-rw-r--r--unix/os/doc/zsvjmp.hlp65
-rw-r--r--unix/os/doc/ztslee.hlp31
-rw-r--r--unix/os/doc/zxgmes.hlp35
-rw-r--r--unix/os/doc/zxwhen.hlp70
-rw-r--r--unix/os/doc/zzclmt.hlp47
-rw-r--r--unix/os/doc/zzopmt.hlp62
-rw-r--r--unix/os/doc/zzrdmt.hlp37
-rw-r--r--unix/os/doc/zzrwmt.hlp31
-rw-r--r--unix/os/doc/zzwrmt.hlp36
-rw-r--r--unix/os/doc/zzwtmt.hlp41
-rw-r--r--unix/os/getproc.c134
-rw-r--r--unix/os/gmttolst.c73
-rw-r--r--unix/os/irafpath.c165
-rw-r--r--unix/os/mkpkg98
-rw-r--r--unix/os/mkpkg.sh42
-rwxr-xr-xunix/os/mkproto5
-rw-r--r--unix/os/net/README90
-rw-r--r--unix/os/net/accept.c26
-rw-r--r--unix/os/net/connect.c27
-rw-r--r--unix/os/net/ctype.h4
-rw-r--r--unix/os/net/eprintf.c15
-rw-r--r--unix/os/net/ghostbynm.c37
-rw-r--r--unix/os/net/ghostent.c137
-rw-r--r--unix/os/net/gsocknm.c23
-rw-r--r--unix/os/net/hostdb.c39
-rw-r--r--unix/os/net/htonl.c22
-rw-r--r--unix/os/net/htons.c16
-rw-r--r--unix/os/net/in.h134
-rw-r--r--unix/os/net/inetaddr.c92
-rw-r--r--unix/os/net/kutil.c342
-rw-r--r--unix/os/net/listen.c22
-rw-r--r--unix/os/net/mkpkg25
-rw-r--r--unix/os/net/netdb.h44
-rw-r--r--unix/os/net/ntohl.c22
-rw-r--r--unix/os/net/ntohs.c16
-rw-r--r--unix/os/net/rexec.c160
-rw-r--r--unix/os/net/socket.c25
-rw-r--r--unix/os/net/socket.h109
-rw-r--r--unix/os/net/tcpclose.c16
-rw-r--r--unix/os/net/tcpread.c26
-rw-r--r--unix/os/net/tcpwrite.c23
-rw-r--r--unix/os/net/types.h39
-rw-r--r--unix/os/net/zfioks.c441
-rw-r--r--unix/os/net/zzdebug.x92
-rw-r--r--unix/os/prwait.c175
-rw-r--r--unix/os/tape.c508
-rw-r--r--unix/os/zalloc.c206
-rw-r--r--unix/os/zawset.c154
-rw-r--r--unix/os/zcall.c91
-rw-r--r--unix/os/zdojmp.c38
-rw-r--r--unix/os/zfacss.c124
-rw-r--r--unix/os/zfaloc.c104
-rw-r--r--unix/os/zfchdr.c57
-rw-r--r--unix/os/zfdele.c27
-rw-r--r--unix/os/zfgcwd.c65
-rw-r--r--unix/os/zfinfo.c99
-rw-r--r--unix/os/zfiobf.c888
-rw-r--r--unix/os/zfioks.c2101
-rw-r--r--unix/os/zfiolp.c239
-rw-r--r--unix/os/zfiomt.c1911
-rw-r--r--unix/os/zfiond.c918
-rw-r--r--unix/os/zfiopl.c279
-rw-r--r--unix/os/zfiopr.c499
-rw-r--r--unix/os/zfiosf.c126
-rw-r--r--unix/os/zfiotx.c991
-rw-r--r--unix/os/zfioty.c127
-rw-r--r--unix/os/zflink.c45
-rw-r--r--unix/os/zfmkcp.c71
-rw-r--r--unix/os/zfmkdr.c44
-rw-r--r--unix/os/zfnbrk.c63
-rw-r--r--unix/os/zfpath.c50
-rw-r--r--unix/os/zfpoll.c129
-rw-r--r--unix/os/zfprot.c103
-rw-r--r--unix/os/zfrmdr.c39
-rw-r--r--unix/os/zfrnam.c50
-rw-r--r--unix/os/zfsubd.c104
-rw-r--r--unix/os/zfunc.c80
-rw-r--r--unix/os/zfutim.c68
-rw-r--r--unix/os/zfxdir.c51
-rw-r--r--unix/os/zgcmdl.c91
-rw-r--r--unix/os/zghost.c25
-rw-r--r--unix/os/zglobl.c19
-rw-r--r--unix/os/zgmtco.c49
-rw-r--r--unix/os/zgtenv.c245
-rw-r--r--unix/os/zgtime.c65
-rw-r--r--unix/os/zgtpid.c18
-rw-r--r--unix/os/zintpr.c29
-rw-r--r--unix/os/zlocpr.c61
-rw-r--r--unix/os/zlocva.c24
-rw-r--r--unix/os/zmain.c204
-rw-r--r--unix/os/zmaloc.c39
-rw-r--r--unix/os/zmfree.c35
-rw-r--r--unix/os/zopdir.c468
-rw-r--r--unix/os/zopdpr.c201
-rw-r--r--unix/os/zoscmd.c219
-rw-r--r--unix/os/zpanic.c103
-rw-r--r--unix/os/zraloc.c37
-rw-r--r--unix/os/zshlib.c18
-rw-r--r--unix/os/zwmsec.c109
-rw-r--r--unix/os/zxwhen.c499
-rw-r--r--unix/os/zzdbg.c158
-rw-r--r--unix/os/zzepro.c84
-rw-r--r--unix/os/zzexit.c17
-rw-r--r--unix/os/zzpstr.c176
-rw-r--r--unix/os/zzsetk.c38
-rw-r--r--unix/os/zzstrt.c628
167 files changed, 24463 insertions, 0 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <utmpx.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <string.h>
+
+#define import_spp
+#define import_alloc
+#define import_knames
+#include <iraf.h>
+
+/*
+ * 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 <finfo.h>
+
+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 <config.h>
+
+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 <when.h>
+
+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 <when.h>, 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 <sys/types.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include <sys/proc.h>
+#include <stdio.h>
+#include <nlist.h>
+
+#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 <stdio.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+/* 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 <sys/types.h>
+#ifdef SYSV
+#include <time.h>
+#else
+#include <sys/time.h>
+#include <sys/timeb.h>
+#endif
+
+#ifdef MACOSX
+#include <time.h>
+#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 <stdio.h>
+#include <ctype.h>
+
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+#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 <libc/knames.h>.
+
+$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 <libc/kernel.h> <libc/spp.h>
+ $endif
+
+ irafpath.c <libc/kernel.h> <libc/spp.h>
+ gmttolst.c
+ prwait.c <libc/kernel.h> <libc/spp.h>
+ zalloc.c <libc/spp.h> <libc/alloc.h> <libc/kernel.h>
+ zawset.c <libc/kernel.h> <libc/spp.h>
+ zdojmp.c <libc/kernel.h> <libc/spp.h>
+ zcall.c <libc/kernel.h> <libc/spp.h>
+ zfunc.c <libc/kernel.h> <libc/spp.h>
+ zfacss.c <libc/kernel.h> <libc/spp.h>
+ zfaloc.c <libc/kernel.h> <libc/spp.h>
+ zfchdr.c <libc/kernel.h> <libc/spp.h>
+ zfdele.c <libc/kernel.h> <libc/spp.h>
+ zfgcwd.c <libc/kernel.h> <libc/spp.h>
+ zfinfo.c <libc/kernel.h> <libc/spp.h> <libc/finfo.h>
+ zfiobf.c <libc/kernel.h> <libc/spp.h>
+ zfioks.c <libc/kernel.h> <libc/spp.h>
+ zfiolp.c <libc/kernel.h> <libc/spp.h>
+ zfiond.c <libc/kernel.h> <libc/spp.h>
+ zfiomt.c <libc/kernel.h> <libc/spp.h>
+ zfiopl.c <libc/kernel.h> <libc/spp.h>
+ zfiopr.c <libc/kernel.h> <libc/spp.h>
+ zfiosf.c <libc/spp.h>
+ zfiotx.c <libc/kernel.h> <libc/spp.h>
+ zfioty.c <libc/spp.h>
+ zfmkcp.c <libc/kernel.h> <libc/spp.h>
+ zfmkdr.c <libc/kernel.h> <libc/spp.h>
+ zfnbrk.c <libc/kernel.h> <libc/spp.h>
+ zfpath.c <libc/kernel.h> <libc/spp.h>
+ zfpoll.c <libc/kernel.h> <libc/spp.h>
+ zfprot.c <libc/kernel.h> <libc/spp.h>
+ zfrnam.c <libc/kernel.h> <libc/spp.h>
+ zfrmdr.c <libc/kernel.h> <libc/spp.h>
+ zfsubd.c <libc/kernel.h> <libc/spp.h>
+ zfutim.c <libc/kernel.h> <libc/spp.h>
+ zfxdir.c <libc/kernel.h> <libc/spp.h>
+ zgcmdl.c <libc/kernel.h> <libc/spp.h>
+ zghost.c <libc/kernel.h> <libc/spp.h>
+ zglobl.c <libc/kernel.h> <libc/spp.h>
+ zgmtco.c <libc/kernel.h> <libc/spp.h>
+ zgtenv.c <libc/kernel.h> <libc/spp.h>
+ zgtime.c <libc/kernel.h> <libc/spp.h>
+ zgtpid.c <libc/kernel.h> <libc/spp.h>
+ zintpr.c <libc/kernel.h> <libc/spp.h>
+ zlocpr.c <libc/kernel.h> <libc/spp.h>
+ zlocva.c <libc/kernel.h> <libc/spp.h>
+ zmaloc.c <libc/kernel.h> <libc/spp.h>
+ zmfree.c <libc/kernel.h> <libc/spp.h>
+ zopdir.c <libc/kernel.h> <libc/spp.h>
+ zopdpr.c <libc/kernel.h> <libc/spp.h>
+ zoscmd.c <libc/kernel.h> <libc/spp.h> <libc/error.h>
+ zpanic.c <libc/kernel.h> <libc/spp.h>
+ zraloc.c <libc/kernel.h> <libc/spp.h>
+ zshlib.c
+ zwmsec.c <libc/kernel.h> <libc/spp.h>
+ zxwhen.c <libc/kernel.h> <libc/spp.h>
+ zzepro.c <libc/spp.h>
+ zzexit.c <libc/spp.h>
+ zzpstr.c <libc/spp.h>
+ zzsetk.c <libc/spp.h>
+ 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 <stdio.h>
+
+/* 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 <stdio.h>
+#include "netdb.h"
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+
+/* 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 <stdio.h>
+#include "types.h"
+#include "netdb.h"
+#include "socket.h"
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+#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 <stdio.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+
+/*
+ * 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 <iraf.h>. 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 <stdio.h>
+#include <ctype.h>
+#include <signal.h>
+#include <setjmp.h>
+
+#include "types.h"
+#include "in.h"
+
+#define import_kernel
+#define import_knames
+#define import_zfstat
+#define import_spp
+#include <iraf.h>
+
+/* 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 "<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 <stdio.h>
+#include <sys/wait.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <sys/wait.h>.
+ */
+#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 <stdio.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mtio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef sun
+#include <sundev/tmreg.h>
+#include <sundev/xtreg.h>
+#include <sundev/arreg.h>
+#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 <file> 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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <utmpx.h>
+#include <pwd.h>
+
+#define import_spp
+#define import_alloc
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/*
+ * 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 <stdio.h>
+#include <unistd.h>
+#ifndef NORLIMIT
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+#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 <stdio.h>
+
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/* 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 <setjmp.h>
+
+#include <stdio.h>
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+#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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#define import_finfo
+#include <iraf.h>
+
+/* 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<<bit: 0;
+ }
+
+ /* Get owner name. Once the owner name string has been retrieved
+ * for a particular (system wide unique) UID, cache it, to speed
+ * up multiple requests for the same UID.
+ */
+ {
+ static int uid = 0;
+ static char owner[SZ_OWNERSTR+1];
+ struct passwd *pw;
+
+ if (osfile.st_uid == uid)
+ strncpy ((char *)fs->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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <ctype.h>
+#include <unistd.h>
+
+# ifndef O_NDELAY
+#include <fcntl.h>
+# endif
+
+#include <errno.h>
+#include <stdio.h>
+
+#define import_kernel
+#define import_knames
+#define import_zfstat
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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 <signal.h>
+
+#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 <stdio.h>
+#include <ctype.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <time.h>
+#include <pwd.h>
+
+#ifdef SYSV
+#include <termios.h>
+#else
+#include <sgtty.h>
+#endif
+
+#ifdef MACOSX
+#define USE_RCMD 1
+#include <unistd.h>
+#endif
+
+#define import_kernel
+#define import_knames
+#define import_zfstat
+#define import_prtype
+#define import_spp
+#include <iraf.h>
+
+
+/* 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 "<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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#define import_kernel
+#define import_knames
+#define import_zfstat
+#define import_prtype
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/errno.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <pwd.h>
+
+#ifdef _AIX
+#include <sys/tape.h>
+#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 <sys/mtio.h>
+#endif
+#endif
+
+/* Define if status logging to sockets is desired. */
+#define TCPIP
+
+#ifdef TCPIP
+#include <signal.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#define DEFPORT 5138
+#endif
+
+#define import_kernel
+#define import_knames
+#define import_zfstat
+#define import_stdarg
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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<lk>.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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <setjmp.h>
+
+#ifdef LINUX
+#include <sys/time.h>
+#endif
+#ifdef MACOSX
+#include <sys/select.h>
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+
+#define import_kernel
+#define import_knames
+#define import_zfstat
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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:
+ *
+ * <domain> : <address> [ : flag ] [ : flag...]
+ *
+ * where <domain> 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:<chan>", where <chan> 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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#define import_kernel
+#define import_knames
+#define import_zfstat
+#define import_prtype
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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 <stdio.h>
+#include <signal.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#define import_kernel
+#define import_knames
+#define import_prtype
+#define import_zfstat
+#define import_spp
+#include <iraf.h>
+
+extern int errno; /* error code returned by the kernel */
+#ifdef SYSV
+#define vfork fork
+#else
+# ifdef sun
+# include <vfork.h>
+# 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 <stdio.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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 <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <errno.h>
+
+#ifdef LINUX
+#define USE_SIGACTION
+#endif
+
+#ifdef SYSV
+#include <termios.h>
+#else
+#include <sgtty.h>
+#endif
+
+#ifndef O_NDELAY
+#include <fcntl.h>
+#endif
+
+#define import_kernel
+#define import_knames
+#define import_zfstat
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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 <stdio.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+
+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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+
+/* 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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define import_kernel
+#define import_knames
+#define import_protect
+#define import_spp
+#include <iraf.h>
+
+/* 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 <sys/stat.h>
+#include <sys/types.h>
+
+#include <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#include <ctype.h>
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#include <ctype.h>
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#include <errno.h>
+#include <sys/poll.h>
+
+#define import_spp
+#define import_kernel
+#define import_knames
+#define import_fpoll
+#include <iraf.h>
+
+
+/* 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 <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define import_kernel
+#define import_knames
+#define import_protect
+#define import_spp
+#include <iraf.h>
+
+#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 <sys/stat.h>
+#include <sys/types.h>
+
+#include <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_protect
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#include <ctype.h>
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifdef SYSV
+#include <time.h>
+#else
+#include <sys/time.h>
+#include <sys/timeb.h>
+#endif
+#include <utime.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+#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 <stdio.h>
+#include <ctype.h>
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+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 <stdio.h>
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#define import_spp
+#define import_kernel
+#include <iraf.h>
+
+#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 <stdio.h>
+#include <sys/types.h>
+#include <time.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+#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(&ltime)->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 <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#define import_spp
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+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 <iraf.h>, 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 <iraf.h> 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 <iraf.h> 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 <iraf.h> 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 <iraf.h> 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 <stdio.h>
+#include <sys/types.h>
+#ifndef SYSV
+#include <sys/timeb.h>
+#endif
+#include <sys/times.h>
+#include <sys/time.h>
+#include <time.h>
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <kernel.h> */
+#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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#include <signal.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+#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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <stdio.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#define import_spp
+#define import_kernel
+#define import_prtype
+#define import_knames
+#define import_xnames
+#include <iraf.h>
+
+/*
+ * 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 <stdio.h>
+#include <stdlib.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+
+
+/* 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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+
+/* 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 <stdio.h>
+#include <sys/types.h>
+
+#ifdef LINUX
+/* Necessary to get DIR.dd_fd on Linux systems. */
+#define DIRENT_ILLEGAL_ACCESS
+#endif
+
+#ifdef POSIX
+#include <dirent.h>
+#else
+#include <sys/dir.h>
+#endif
+
+
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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 <ctype.h>
+#include <stdio.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#define import_spp
+#define import_xwhen
+#define import_kernel
+#define import_knames
+#include <iraf.h>
+
+#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 <stdio.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#define import_kernel
+#define import_knames
+#define import_error
+#define import_spp
+#include <iraf.h>
+
+#ifdef LINUX
+#define USE_SIGACTION
+#endif
+
+static int lastsig;
+extern int pr_onint();
+
+#ifdef SYSV
+#define vfork fork
+#else
+# ifdef sun
+# include <vfork.h>
+# 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 <stdio.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+#define import_kernel
+#define import_knames
+#define import_prtype
+#define import_spp
+#include <iraf.h>
+
+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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <iraf.h>
+
+/*
+ * 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 <stdio.h>
+#define import_kernel
+#define import_knames
+#define import_spp
+#include <iraf.h>
+
+/* 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 <sys/time.h>
+#include <signal.h>
+
+#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 <stdio.h>
+#include <signal.h>
+
+#ifdef CYGWIN
+# include <mingw/fenv.h>
+#else
+#ifdef LINUX
+# include <fpu_control.h>
+#else
+# ifdef BSD
+# include <floatingpoint.h>
+# endif
+#endif
+#endif
+
+#ifdef SOLARIS
+# include <sys/siginfo.h>
+# include <sys/ucontext.h>
+# include <ieeefp.h>
+#endif
+
+#ifdef MACOSX
+#include <math.h>
+#include <fenv.h>
+#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 <iraf.h>
+
+/* 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 <iraf.h>
+ * (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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+
+#define import_spp
+#define import_kernel
+#define import_knames
+#define import_xnames
+#define import_prtype
+#include <iraf.h>
+
+
+
+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 <stdio.h>
+#include <signal.h>
+#ifdef MACOSX
+#include <math.h>
+#include <fenv.h>
+#endif
+#ifdef CYGWIN
+#include <math.h>
+#include <mingw/fenv.h>
+#endif
+
+#define import_spp
+#define import_knames
+#include <iraf.h>
+
+
+
+#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 <stdlib.h>
+
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <fcntl.h>
+
+#define import_spp
+#include <iraf.h>
+
+/*
+ * 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 <stdio.h>
+#include <string.h>
+
+#define import_spp
+#define import_knames
+#include <iraf.h>
+
+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 <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+
+#ifdef CYGWIN
+# include <mingw/fenv.h>
+#else
+#ifdef LINUX
+# include <fpu_control.h>
+# undef SOLARIS
+#endif
+#endif
+
+#ifdef SHLIB
+#ifdef SOLARIS
+#include <libelf.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#else
+#include <sys/mman.h>
+#include <a.out.h>
+#endif
+#endif
+
+#ifdef sun
+#include <floatingpoint.h>
+#endif
+
+#ifdef SOLARIS
+#include <ieeefp.h>
+#endif
+
+#ifdef LINUXPPC
+#define MACUNIX
+#endif
+
+#ifdef MACOSX
+#include <math.h>
+#include <fenv.h>
+#ifndef MACINTEL
+#define MACUNIX
+#endif
+#endif
+
+#define import_spp
+#define import_kernel
+#define import_knames
+#define import_xnames
+#define import_prtype
+#include <iraf.h>
+
+/*
+ * 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 <a.out.h> 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) {}
+