ftp.nice.ch/pub/next/unix/admin/sysinfo.1.1.0.s.tar.gz#/os-ultrix.c

This is os-ultrix.c in view mode; [Download] [Up]

/*
 * Copyright (c) 1992 Michael A. Cooper.
 * This software may be freely distributed provided it is not sold for 
 * profit and the author is credited appropriately.
 */

#ifndef lint
static char *RCSid = "$Header: /src/common/usc/bin/sysinfo/RCS/os-ultrix.c,v 1.17 1992/12/12 23:48:04 mcooper Exp mcooper $";
#endif

/*
 * $Log: os-ultrix.c,v $
 * Revision 1.17  1992/12/12  23:48:04  mcooper
 * Call MkDevName() with correct number of parameters.
 *
 * Revision 1.16  1992/11/24  04:05:57  mcooper
 * New/cleaner KVM/nlist interface.
 *
 * Revision 1.15  1992/07/09  23:21:59  mcooper
 * Defining NEED_ETHER_ADDR will make things work under Ultrix 4.[01].
 *
 * Revision 1.14  1992/04/26  23:32:06  mcooper
 * Add Copyright notice
 *
 * Revision 1.13  1992/04/17  23:27:51  mcooper
 * Add support for ROM Version information (Sun only for now).
 *
 * Revision 1.12  1992/04/17  01:07:59  mcooper
 * More de-linting
 *
 * Revision 1.11  1992/04/16  19:56:24  mcooper
 * De-linting stuff.
 *
 * Revision 1.10  1992/04/15  02:04:16  mcooper
 * Change GetMemoryStr() to GetMemory().
 *
 * Revision 1.9  1992/03/31  02:22:03  mcooper
 * Fix failed return value from CheckNlist().
 *
 * Revision 1.8  1992/03/31  01:55:17  mcooper
 * Use new CheckNlist to check nlist success.
 *
 * Revision 1.7  1992/03/31  00:15:09  mcooper
 * Add error check for nlist.n_type.
 *
 * Revision 1.6  1992/03/22  00:20:10  mcooper
 * Major cleanup and re-org.
 *
 * Revision 1.5  1992/03/09  01:07:50  mcooper
 * Add support for determining system model type.
 *
 * Revision 1.4  1992/03/08  23:08:41  mcooper
 * - Add new set_macinfo_packetfilter().
 * - Remove unneeded get_bus().
 *
 * Revision 1.3  1992/03/08  04:57:09  mcooper
 * Set DT_TAPEDRIVE in probe_tapedrive().
 *
 * Revision 1.2  1992/03/08  01:11:39  mcooper
 * Add tape drive support.
 *
 * Revision 1.1  1992/03/06  18:35:40  mcooper
 * Initial revision
 *
 */

/*
 * Ultrix specific functions
 */

#include <stdio.h>
#include "system.h"
#include "defs.h"

#include <fcntl.h>
#include <nlist.h>
#include <fstab.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/fs.h>
#include <sys/ioctl.h>
#include <sys/buf.h>
#include <sys/stat.h>

#include <machine/cpuconf.h>

#include <sys/devio.h>
#include <sys/mtio.h>

#include "info-ultrix.h"

#if	defined(HAVE_UBA)
/*
 * UBA (UniBus Adapter) specific code
 */
#include <io/uba/ubavar.h>

/*
 * Probe specific structure
 */
struct _probespec {
    struct uba_device		*uba_device;
};
typedef struct _probespec PROBESPEC;

#define DV_SIZE		(sizeof(struct uba_device))
#define CR_SIZE		(sizeof(struct uba_ctlr))

/*
 * Build a device tree by searching Unibus Adapters
 */
static int BuildUBA(TreePtr)
    DEVICE 		      **TreePtr;
{
    extern char		 	UniBusSYM[];
    static struct nlist		nlistbuf;
    struct nlist	       *nlptr;
    static struct uba_device 	Device;
    static struct uba_ctlr 	Ctlr;
    static char 		CtlrName[BUFSIZ], DevName[BUFSIZ];
    u_long 			Addr, DeviceAddr;
    static DEVDATA 		DevData;
    static PROBESPEC		ProbeSpec;
    DEVICE 		       *dev;
    kvm_t		       *kd;
    int 			cnum;

    /*
     * Read table address from kernel
     */
    if (!(kd = KVMopen()))
	return(-1);

    if ((nlptr = KVMnlist(kd, UniBusSYM, &nlistbuf)) == NULL)
	return(-1);

    if (CheckNlist(nlptr))
	return(-1);

    /*
     * Read each device table entry.  A NULL device.ui_driver
     * indicates that we're at the end of the table.
     */
    for (DeviceAddr = nlptr->n_value; DeviceAddr; 
	 DeviceAddr += DV_SIZE) {

	/*
	 * Read this device
	 */
	if (KVMread(kd, DeviceAddr, (char *) &Device, DV_SIZE)) {
	    if (Debug) 
		Error("Cannot read unibus device from address 0x%x.", 
		      DeviceAddr);
	    KVMclose(kd);
	    return(-1);
	}

	/*
	 * See if we're done.
	 */
	if (!Device.ui_driver)
	    break;

	/*
	 * Get the device name
	 */
	DevName[0] = C_NULL;
	if (Addr = (u_long) Device.ui_devname) {
	    if (KVMread(kd, Addr, (char *) DevName, sizeof(DevName))) {
		if (Debug)
		    Error("Cannot read device name from address 0x%x.", Addr);
		continue;
	    }
	}

	/*
	 * Get the controller info
	 */
	CtlrName[0] = C_NULL;
	cnum = -1;
	if (Addr = (u_long) Device.ui_mi) {
	    if (KVMread(kd, Addr, (char *) &Ctlr, CR_SIZE)) {
		if (Debug) 
		    Error("Cannot read controller from address 0x%x.", Addr);
	    } else {
		/*
		 * Get the controller name
		 */
		if (Addr = (u_long) Ctlr.um_ctlrname) {
		    if (KVMread(kd, Addr, CtlrName, sizeof(CtlrName))) {
			if (Debug)
			    Error(
		  "Cannot read controller name from driver address 0x%x.",
				  Addr);
			continue;
		    }
		}
		cnum = Ctlr.um_ctlr;
	    }
	}

	if (Debug)
	    printf("build_unibus(): Found '%s' on '%s'.\n", DevName, CtlrName);

	/* Make sure devdata is clean */
	bzero(&DevData, sizeof(DEVDATA));

	/* Set what we know */
	if (DevName[0]) {
	    DevData.dd_devname = strdup(DevName);
	    DevData.dd_devunit = Device.ui_unit;
	}
	if (CtlrName[0]) {
	    DevData.dd_ctlrname = strdup(CtlrName);
	    DevData.dd_ctlrunit = cnum;
	}

	/* 
	 * Unibus devices should always exist.
	 */
	if (Device.ui_alive)
	    DevData.dd_flags |= DD_IS_ALIVE;

	ProbeSpec.uba_device = &Device;

	/* Probe and add device */
	if (dev = (DEVICE *) ProbeDevice(&DevData, TreePtr, &ProbeSpec))
	    AddDevice(dev, TreePtr);
    }

    KVMclose(kd);

    return(0);
}
#endif	/* HAVE_UBA */

/*
 * Build list of Ultrix devices
 */
extern int BuildDevicesUltrix(TreePtr)
    DEVICE 		      **TreePtr;
{
    int				Found = 1;

#if	defined(HAVE_UBA)
    if (BuildUBA(TreePtr) == 0)
	Found = 0;
#endif	/* HAVE_UBA */

    return(Found);
}

/*
 * Get Device Info structure from device.
 */
static struct devget *GETdevget(File, FileD)
    char		       *File;
    int 			FileD;
{
    static struct devget 	devget;

    if (ioctl(FileD, DEVIOCGET, &devget) == SYSFAIL) {
	if (Debug) Error("%s: ioctl DEVIOCGET failed: %s.", File, SYSERR);
	return((struct devget *) NULL);
    }

    return(&devget);
}

/*
 * Get Device Geometry structure from device.
 */
static DEVGEOMST *GETdevgeom(File, FileD)
    char		       *File;
    int 			FileD;
{
    static DEVGEOMST 		devgeom;

    if (ioctl(FileD, DEVGETGEOM, &devgeom) == SYSFAIL) {
	if (Debug) Error("%s: ioctl DEVGETGEOM failed: %s.", File, SYSERR);
	return((DEVGEOMST *) NULL);
    }

    return(&devgeom);
}

/*
 * Lookup a category type.
 */
static char *GetCategory(val)
    int				val;
{
    extern NAMETAB	        Categorys[];
    register int 		i;

    for (i = 0; Categorys[i].name; ++i)
	if (val == Categorys[i].value)
	    return(Categorys[i].name);

    return((char *) NULL);
}

/*
 * Convert a 'devget' to a 'device'.
 */
static DEVICE *devgetToDEVICE(DevGet, DevData, ProbeSpec)
    struct devget	       *DevGet;
    DEVDATA		       *DevData;
    PROBESPEC		       *ProbeSpec;
{
    DEVICE		       *Device;

    if (!(Device = NewDevice(NULL)))
	return(Device);

    Device->dv_name	= strdup(MkDevName(DevData->dd_devname,
					   DevData->dd_devunit, 0, 0));
    Device->dv_model	= strdup(DevGet->device);
    Device->dv_desc	= GetCategory(DevGet->category);
    Device->dv_unit	= DevGet->unit_num;

    /*
     * Set master/controller info
     */
    if (Device->dv_master = MkMasterFromDevData(DevData))
	Device->dv_master->dv_model = strdup(DevGet->interface);

    return(Device);
}

/*
 * Check a device by trying to perform a devget on it.
 */
static struct devget *CheckDevice(File)
    char		       *File;
{
    struct devget	       *DevGet;
    int				d;

    if ((d = open(File, O_RDONLY|O_NDELAY)) < 0) {
	if (Debug) Error("%s: Cannot open: %s.", File, SYSERR);
	return((struct devget *) NULL);
    }

    /*
     * Get generic device info
     */
    if (!(DevGet = GETdevget(File, d))) {
	if (Debug) Error("%s: GETdevget failed.", File);
	close(d);
	return((struct devget *) NULL);
    }

    close(d);

    return(DevGet);
}

/*
 * Retrieve disk partition information from a device file.
 */
static struct pt *ExtractDiskPart(File)
    char		       *File;
{
    static struct pt		pt;
    int				d;

    if ((d = open(File, O_RDONLY|O_NDELAY)) < 0) {
	if (Debug) Error("%s: open failed: %s.", File, SYSERR);
	return((struct pt *) NULL);
    }

    if (ioctl(d, DIOCGETPT, &pt) < 0) {
	if (Debug) Error("%s: ioctl DIOCGETPT failed: %s.", File, SYSERR);
	close(d);
	return((struct pt *) NULL);
    }

    close(d);

    return(&pt);
}

/*
 * Get the mount point for a filesystem.
 */
static char *GetMountInfo(Name, Part)
    char		       *Name;
    char		       *Part;
{
    char		       *File;
    struct fstab	       *fstab;

    File = GetCharFile(Name, Part);
    if (fstab = getfsspec(File)) {
	if (strcmp(fstab->fs_type, FSTAB_SW) == 0)
	    return("swap");
	return(fstab->fs_file);
    }

    return((char *) NULL);
}

/*
 * Get the partition information for a disk device.
 */
static DISKPART *GetPartInfo(Name, Device)
    char 		       *Name;
    DEVICE		       *Device;
{
    static DISKPART	        diskpart;
    static char			Buf[BUFSIZ], part[2];
    register DISKPART	       *pdp, *dp;
    register char	       *p;
    DISKPART		       *base = NULL;
    struct pt		       *pt;
    register int		i;

    /*
     * First get the partition info.
     */
    (void) sprintf(Buf, "/dev/r%sa", Name);
    if (!(pt = ExtractDiskPart(Buf)))
	return((DISKPART *) NULL);

    part[1] = C_NULL;

    /*
     * Now deal with each partition.
     */
    for (i = 0; i < MAX_DISK_PARTS; ++i) {
	/* Ignore partitions that have no size */
	if (!pt->pt_part[i].pi_nblocks)
	    continue;

	part[0] = 'a' + i;

	/* Make a clean slate */
	bzero((char *) &diskpart, sizeof(DISKPART));

	/* Fill in what we know */
	diskpart.dp_name = strdup(part);
	diskpart.dp_stsect = pt->pt_part[i].pi_blkoff;
	diskpart.dp_nsect = pt->pt_part[i].pi_nblocks;

	/* 
	 * Get the mount point name.
	 * If this is the "b" partition on the 
	 * root device, then assume it's swap 
	 */
	if (p = GetMountInfo(Name, part))
	    diskpart.dp_mnt = strdup(p);
	else if (Device->dv_unit == 0 && strcmp(part, "b") == 0)
	    diskpart.dp_mnt = "swap";

	/*
	 * Add this partition to the linked list.
	 */
	if (base) {
	    for (pdp = base; pdp && pdp->dp_nxt; pdp = pdp->dp_nxt);
	    pdp->dp_nxt = NewDiskPart(&diskpart);
	} else {
	    base = NewDiskPart(&diskpart);
	}
    }

    return(base);
}

/*
 * Probe a disk drive
 */
extern DEVICE *ProbeDiskDrive(Name, DevData, DevDataTab, ProbeSpec)
    char		       *Name;
    DEVDATA		       *DevData;
    DEVDATATAB		       *DevDataTab;
    PROBESPEC		       *ProbeSpec;
{
    DEVICE		       *Device;
    DISKDRIVE		       *DiskDrive;
    DEVGEOMST		       *DevGeom;
    struct devget	       *DevGet;
    char		       *File;
    int				Desc;

    if (!Name)
	return((DEVICE *) NULL);

    File = GetRawFile(Name, "c");
    if ((Desc = open(File, O_RDONLY|O_NDELAY)) < 0) {
	if (Debug) Error("%s: open failed: %s.", File, SYSERR);
	/*
	 * If we know for sure this drive is present and we
	 * know something about it, then create a minimal device.
	 */
	if ((DevDataTab->ddt_model || DevDataTab->ddt_desc) &&
	    FLAGS_ON(DevData->dd_flags, DD_IS_ALIVE)) {
	    Device = NewDevice((DEVICE *) NULL);
	    Device->dv_name = strdup(Name);
	    Device->dv_unit = DevData->dd_devunit;
	    Device->dv_master = MkMasterFromDevData(DevData);
	    Device->dv_type = DT_DISKDRIVE;
	    Device->dv_model = DevDataTab->ddt_model;
	    Device->dv_desc = DevDataTab->ddt_desc;
	    return(Device);
	} else
	    return((DEVICE *) NULL);
    }

    /*
     * Get generic device info
     */
    if (!(DevGet = GETdevget(File, Desc))) {
	if (Debug) Error("%s: GETdevget failed.", File);
	close(Desc);
	return((DEVICE *) NULL);
    }

    /*
     * Get geometry of device
     */
    if (!(DevGeom = GETdevgeom(File, Desc))) {
	if (Debug) Error("%s: get_geomst failed.", File);
    }

    close(Desc);

    /*
     * Convert devget info to a device struct
     */
    if (!(Device = devgetToDEVICE(DevGet, DevData, ProbeSpec))) {
	if (Debug) Error("%s: Cannot convert devget to device.");
	return((DEVICE *) NULL);
    }

    /*
     * Disks should be on disk controllers.
     */
    if (Device->dv_master)
	Device->dv_master->dv_type = DT_DISKCTLR;

    /*
     * Set the disk drive specific info
     */

    if ((DiskDrive = NewDiskDrive(NULL)) == NULL) {
	Error("Cannot create new diskdrive entry.");
	return((DEVICE *) NULL);
    }

    Device->dv_type 		= DT_DISKDRIVE;
    if (DevGet->device)
	DiskDrive->dd_label 	= strdup(DevGet->device);

    /*
     * Convert Geometry
     */
    if (DevGeom) {
	/*
	 * If this is a removable device, indicate so.
	 */
	if (FLAGS_ON(DevGeom->geom_info.attributes, DEVGEOM_REMOVE)) {
	    if (Device->dv_desc) {
		char Buf[BUFSIZ];

		(void) sprintf(Buf, "Removable %s", Device->dv_desc);
		(void) free(Device->dv_desc);
		Device->dv_desc = strdup(Buf);
	    } else {
		Device->dv_desc = "Removable disk drive";
	    }
	}

	DiskDrive->dd_unit 	= DevGet->unit_num;
	DiskDrive->dd_slave 	= DevGet->slave_num;
	DiskDrive->dd_part 	= GetPartInfo(Name, Device);
	DiskDrive->dd_dcyl 	= DevGeom->geom_info.ncylinders;
	DiskDrive->dd_heads 	= DevGeom->geom_info.ntracks;
	DiskDrive->dd_sect 	= DevGeom->geom_info.nsectors;
	DiskDrive->dd_secsize 	= SECSIZE;

	if (DiskDrive->dd_dcyl && DiskDrive->dd_sect && DiskDrive->dd_heads) {
	    static char Buf[BUFSIZ];

	    DiskDrive->dd_size = nsect_to_bytes(DiskDrive->dd_dcyl * 
						DiskDrive->dd_sect * 
						DiskDrive->dd_heads, 
						DiskDrive->dd_secsize);

	    (void) sprintf(Buf, "%.2f MB capacity", 
			   (float) bytes_to_mbytes(DiskDrive->dd_size));
	    Device->dv_desc = strdup(Buf);
	}
    }
    Device->dv_devspec = (caddr_t *) DiskDrive;

    return(Device);
}

/*
 * Lookup info about a tape drive.
 */
static char *GetTapeInfo(Flag)
    int				Flag;
{
    extern NAMETAB		TapeInfo[];
    static char			Buf[BUFSIZ];
    register int		i;

    if (!Flag)
	return((char *) NULL);

    Buf[0] = C_NULL;

    /*
     * Values are flag bits and are appended together.
     */
    for (i = 0; TapeInfo[i].name; i++) {
	if (Flag & TapeInfo[i].value) {
	    if (Buf[0]) {
		(void) strcat(Buf, ", ");
		(void) strcat(Buf, TapeInfo[i].name);
	    } else
		(void) strcpy(Buf, TapeInfo[i].name);
	}
    }

    return(Buf);
}

/*
 * Probe a tape drive
 */
extern DEVICE *ProbeTapeDrive(Name, DevData, DevDataTab, ProbeSpec)
    char		       *Name;
    DEVDATA		       *DevData;
    DEVDATATAB		       *DevDataTab;
    PROBESPEC		       *ProbeSpec;
{
    struct devget	       *DevGet;
    DEVICE		       *Device;
    char		       *File;
    char		       *p;
    char		        Buf[BUFSIZ];
    register int		i;

    /*
     * XXX Kludge Alert! ! !
     *
     * Ultrix tape device files are numbered independently of actual
     * unit number.  Additionally, not all tape devices support the same
     * set of minor devices types, so we can't look at the minor device 
     * number.  
     *
     * The code below will open(), ioctl(), close() all tape
     * devices between 0 and MAXTAPES until a matching unit number is found.
     * This means that on systems with lots of tape drives, this can be 
     * very slow.
     */
    for (i = 0; i < MAXTAPES; ++i) {
	(void) sprintf(Buf, "/dev/nrmt%dh", i);
	if ((DevGet = CheckDevice(Buf)) && 
	    (DevGet->unit_num == DevData->dd_devunit))
	    break;
    }

    if (!DevGet) {
	if (Debug) Error("%s: Cannot find device file.", Name);
	return((DEVICE *) NULL);
    }

    /*
     * Convert devget info to a device struct
     */
    if (!(Device = devgetToDEVICE(DevGet, DevData, ProbeSpec))) {
	if (Debug) Error("%s: Cannot convert devget to device.");
	return((DEVICE *) NULL);
    }

    /*
     * Set our device type
     */
    Device->dv_type = DT_TAPEDRIVE;

    /*
     * Get and add Tape Info
     */
    if (p = GetTapeInfo(DevGet->category_stat)) {
	if (Device->dv_desc) {
	    (void) sprintf(Buf, "%s %s", p, Device->dv_desc);
	    free(Device->dv_desc);
	    Device->dv_desc = strdup(Buf);
	} else {
	    Device->dv_desc = p;
	}
    }

    /*
     * Tapes should be on tape controllers.
     */
    if (Device->dv_master)
	Device->dv_master->dv_type = DT_TAPECTLR;

    return(Device);
}

/*
 * Get network type information
 */
static char *GetNetType(type)
    int				type;
{
    extern NAMETAB		NetTypes[];
    register int		i;

    for (i = 0; NetTypes[i].name; i++)
	if (NetTypes[i].value == type)
	    return(NetTypes[i].name);

    return((char *) NULL);
}


#if	defined(HAVE_PACKETFILTER)

#include <sys/time.h>
#include <net/pfilt.h>

#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>

#if	defined(NEED_ETHER_ADDR)
/*
 * This didn't appear in <netinet/if_ether.h> until Ultrix 4.2 (4.1?)
 */
struct ether_addr {
	u_char	ether_addr_octet[6];
};
#endif	/* NEED_ETHER_ADDR */

/*
 * Find and set the MAC info using the Packet Filter
 */
extern void SetMacInfoPacketFilter(DevName, Netif, Device)
     char 		       *DevName;
     NETIF 		       *Netif;
     DEVICE		       *Device;
{
    struct endevp		endevp;
    struct ether_addr		ether_addr;
    char 		       *ether_ntoa(), HostBuf[MAXHOSTNAMLEN+1];
    char 		       *p;
    int 		        Desc;

    if (!DevName || !Netif)
	return;

    /*
     * Open this device using the packet filter
     */
    if ((Desc = pfopen(DevName, O_RDONLY)) < 0) {
	if (Debug) Error("pfopen %s failed: %s.", DevName, SYSERR);
	return;
    }

    /*
     * Retrieve info
     */
    if (ioctl(Desc, EIOCDEVP, &endevp) < 0) {
	if (Debug) Error("ioctl EIOCDEVP of %s failed: %s.", DevName, SYSERR);
	return;
    }

    close(Desc);

    /*
     * Convert address into ethers(5) format
     */
    bcopy((char *) endevp.end_addr,
	  (char *) ether_addr.ether_addr_octet,
	  endevp.end_addr_len);

    /*
     * Set what we now know.
     */
    if (p = ether_ntoa(&ether_addr))
	Netif->ni_macaddr = strdup(p);

    if (ether_ntohost(HostBuf, &ether_addr) == 0)
	Netif->ni_macname = strdup(HostBuf);

    if (Device && (p = GetNetType(endevp.end_dev_type)))
	Device->dv_desc = p;
}
#endif	/* HAVE_PACKETFILTER */

/*
 * Get the system model name.  Ultrix keeps the system type
 * in a kernel structure called cpusw as cpusw.system_type.
 * The system types are defined in <machine/cpuconf.h>.
 */
extern char *GetModelName()
{
    extern NAMETAB		ModelTab[];
    struct nlist	       *nlptr;
    extern char			CpuSwSYM[];
    static struct cpusw	        CpuSw;
    register int		i;
    kvm_t		       *kd;

    if (!(kd = KVMopen()))
	return((char *) NULL);

    if ((nlptr = KVMnlist(kd, CpuSwSYM, (struct nlist *)NULL)) == NULL)
	return((char *) NULL);

    if (CheckNlist(nlptr))
	return((char *) NULL);

    if (KVMread(kd, (u_long) nlptr->n_value, (char *) &CpuSw,
		sizeof(struct cpusw))) {
	if (Debug) Error("Cannot read cpusw from kernel.");
	return((char *) NULL);
    }

    KVMclose(kd);

    for (i = 0; ModelTab[i].name; ++i)
	if (ModelTab[i].value == CpuSw.system_type)
	    return(ModelTab[i].name);

    if (Debug)
	printf("system model/type %d is unknown.\n", CpuSw.system_type);

    return((char *) NULL);
}

/*
 * Get kernel version string from kernel symbol "version".
 */
extern char *GetKernelVersionStr()
{
    return(GetKernelVersionFromVersion());
}

/*
 * Get amount of physical memory using kernel symbol "physmem".
 */
extern char *GetMemory()
{
    return(GetMemoryFromPhysmem());
}

/*
 * Get system serial number
 */
extern char *GetSerialNoStr()
{
    /* No support */
    return((char *) NULL);
}

/*
 * Get name of OS
 */
extern char *GetOSNameStr()
{
    return(GetOSNameFromUname());
}

/*
 * Get version of OS
 */
extern char *GetOSVersionStr()
{
    return(GetOSVersionFromUname());
}

/*
 * Get ROM Version
 */
extern char *GetRomVer()
{
    /* No support */
    return((char *) NULL);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.