Leak Checking with the Sun C Compiler

Whilst developing POSIX *env API: unleaking portable version, it was necessary to lean on a useful component of the Sun C Compiler. And how – I made plenty of mistakes!

When coding tricksy conditional-deallocation based on tracked pointers where the tracking-array is also in allocated heap memory, and needs to be frequently resized, the possibility of an accidental memory-leak or duplicate-free() is high.

The Sun C Compiler (since version 3.01, approx 1993) comes with a debugger (dbx) capable of automatically detecting these kinds of memory-access/usage mistakes – at runtime.

In particular, even the 1993-vintage dbx detects:

  1. Deplicate Free – passing the address of an already-freed heap block to free()
  2. Bad Free – passing the address of a non-heap object to free()
  3. Read From Unallocated – trying to access through a pointer that does not point to a valid part of the address-space (NULL pointer, wild pointer).
  4. Read From Unitialised – accessing an object that has not been initialised.
  5. Write to Unallocated Memory – trying to update through a pointer that does not point to a valid part of the address-space (NULL pointer, wild pointer).
  6. Heap Memory Leak – there are no (surviving) references to any part of an allocated block.
  7. Lonely Heap Address in Middle of Block(extremely probable memory leak) – there is no reference to the start of an allocated block, but there is at least one reference to an address within the block.
  8. Lonely Heap Address in Register (probable memory leak) – an allocated block has not been freed, and no reference to the block exists anywhere in program memory, but a reference exists in a register.
  9. The newer versions of dbx can detect other less-likely errors (mainly misaligned pointers).

    These checks can be enabled with the “check -all ; check -access” command in either the GUI-debugger command window or to the command-prompt of the character-mode debugger.

    No special alternate libraries or source-code modifications are necessary – just compile with the “debug” flag (-g) and the resulting binary executable can be used both for real runtime production use and for memory-access checking, if desired.

POSIX *env API: unleaking portable version

Carrying on from A POSIX Env Game, below is a portable unleaking implementation of the POSIX 1003.1-2001 putenv/setenv/unsetenv/getenv API.

In addition to all the strctural API idiosyncracies noted in previous article, it also has to deal with the fact that the signature of putenv() changed between XPG4 and POSIX.1-2001, so we have to resort to conditional C preprocessor statements to allow the required signature to be selected – see the PUTENV_CONST macro.

/* 100% portable implementation of the putenv(), setenv(),unsetenv() functions
 * as specified by POSIX.1-2001.
 *
 * Does not rely on any system-specifics or runtime-library internal
 * implementation details [is the env-list initially on the heap or stack (or
 * even in read-only memory)? how about the pointed-to strings? does the heap
 * grow up or down in the address-space?]
 *
 * Allows setenv()/unsetenv() [allocating/deallocating] to co-exist with
 * putenv() [non-allocating] and even be applied to variables created/updated
 * by the other, without any lossage or leaks.
 */

#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#ifdef DEBUG
#include <stdio.h>
#define trace(x)	printf x
#undef NDEBUG
#else
#define trace(x)
#define NDEBUG 1
#endif

extern char **environ;
static char **heaped = NULL; /* indicator for the environ list-array itself,
				not the strings */

static char **tracked = NULL;	/* list of ptrs to self-allocated strings */
static int ntracked = 0;

static void *buy(void *old, size_t size)
	{
	void *p;

	/* careful to deal with differences between C89 and C99 realloc()... */
	p = old ? realloc(old, size ? size : 1) : malloc(size);
	return (p);
	}

static char **heapify_list(void)
	{
	char **p;
	int n;

	assert(heaped != environ);
	trace(("heapifying the env list @%p\n", environ));

	if (heaped)
		{
		trace(("freeing old env list @%p\n", heaped));
		free(heaped);
		}

	for (p = environ, n = 1; *p; ++p, ++n)
		;
	if (!(heaped = malloc(n * sizeof(*environ))))
		{
		trace(("heapify failed\n"));
		return (NULL);
		}
	for (p = heaped; *environ; ++p, ++environ)
		*p = *environ;
	*p = NULL;
	environ = heaped;
	trace(("heapification complete, %i slots @%p\n", n, environ));
	return (environ);
	}

static char **lookup(const char *name, int len)
	{
	char **p;

	trace(("lookup(\"%*.*s\", %i)  ", len, len, name, len));
	for (p = environ; *p; ++p)
		{
		if (!strncmp(*p, name, len) && (*p)[len] == '=')
			{
			trace(("found \"%s\"\n", *p));
			return (p);
			}
		}
	trace(("not found\n"));
	return (NULL);
	}

#define TRACK 1
#define NOTRACK 0

static int is_tracked(char *ptr)
	{
	int i;

	for (i = 0; i < ntracked; ++i)
		{
		if (tracked[i] == ptr)
			return (i);
		}
	return (-1);
	}

static int track(char *ptr)
	{
	char **newlist;

	/* create-or-grow tracking list and add ptr to end of list */
	if (!(newlist = buy(tracked, (ntracked+1) * sizeof(*tracked))))
		return (0);
	tracked = newlist;
	tracked[ntracked++] = ptr;
	return (1);
	}

static int untrack(int idx)
	{
	char **newlist;

	assert(idx < ntracked);
	free(tracked[idx]);

	/* move trailing entry into the hole */
	tracked[idx] = tracked[--ntracked];

	/* trim the trailing entry from the list by resizing */
	if (!(newlist = buy(tracked, ntracked * sizeof(*tracked))))
		return (0);
	tracked = newlist;
	return (1);
	}

static void retrack(int old, char *new)
	{
	assert(old < ntracked);
	free(tracked[old]);
	tracked[old] = new;
	}

static void unleak(void)
	{
	char **e;
	int n;

	for (n = 0; n = 0)
	        {
		trace(("untracking %s\n", tracked[n]));
		if (!untrack(n))
			return (-1);
			}
		/* update main list */
		for (q = p; *q; ++q)
			;
		if (--q >= p)
			*p = *q;	/* move trailing entry */
		*q = NULL;		/* move terminating NULL up */
		n = 1 + q - environ;
		q = buy(environ, n * sizeof(*environ));
		if (!q)
			return (-1);
		trace(("shrunk list, %i slots @%p\n", n, q));
		heaped = environ = q;
		}
	return (0);
	}
	
	if ((p = lookup(str, namelen))) /* env variable is already present */
		{
		if (!overwrite)
			{
			if (type == TRACK)
				free(str);
			return (0);
			}
		if ((n = is_tracked(*p)) >= 0)
			{
			trace(("old was tracked\n"));
			if (type == TRACK)
				retrack(n, str);
			else if (!untrack(n))
				return (-1);
			}
		else	/* old is from putenv */
			{
			trace(("old was untracked\n"));
			if (type == TRACK)
				{
				if (!track(str))
					return (-1);
				}
			}
		*p = str; /* update main list */
		}
	else	/* env variable is NOT already present */
		{
		/* resize the list */
		for (n = 0, p = environ; *p; ++p, ++n)
			;
		if (!(p = buy(environ, (n+2) * sizeof(*environ))))
			return (-1);
		trace(("grown list, %i slots @%p\n", n+2, p));
		if (type == TRACK)
			{
			if (!track(str))
				return (-1);
			}
		/* and insert new entry */
		p[n++] = str;
		p[n] = NULL;
		heaped = environ = p;
		}
	return (0);
	}

/* Several (older, obsolete) API standards declare putenv() with a
 * const-qualified argument:
 *	POSIX 1003.1-1990, 1998
 *	System V Interface Definition, version 3 (1989)
 *	Single UNIX Specification, version 1 (1995)
 *	UNIX95
 * whereas several newer API standards do not:
 *	System V Interface Definition, version 4
 * 	UNIX98
 *	X/Open Portability Guide, version 4
 *	POSIX 1003.1-2001
 * and some do not define putenv() at all (so we could use non-const for them
 * except that we cannot distinguish them from other consted API environments,
 * dammit):
 *	POSIX 1003.1-1988
 */
#if defined(PUTENV_CONST)	/* so can force const override if need be */
#define QUALIFIER const
#elif defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200101L)	/*POSIX.1-2001*/
#define QUALIFIER
#elif defined(_XOPEN_SOURCE) && defined(_XOPEN_VERSION) 	/* XPG4 */
#define QUALIFIER
#elif defined(_XOPEN_SOURCE) && defined(_XOPEN_SOURCE_EXTENDED) && \
		(_XOPEN_SOURCE_EXTENDED >= 1)			/* XPG 4.2 */
#define QUALIFIER
#else
#define QUALIFIER const
#endif
int putenv(QUALIFIER char *str)
	{
	int i, n;

	trace(("putenv(\"%s\")\n", str));
	i = (update((char *)str, NOTRACK, 1));
	return(i);
	}

int setenv(char *name, char *value, int overwrite)
	{
	char *str;

	if (!(str = malloc(strlen(name)+1+strlen(value)+1)))
		return (-1);
	sprintf(str, "%s=%s", name, value);
	trace(("setenv(\"%s\", \"%s\", %i) [%i]\n",
		name, value, overwrite, strlen(str)+1));
	return (update(str, TRACK, overwrite));
	}

int unsetenv(char *name)
	{
	trace(("unsetenv(\"%s\")\n", name));
	return (update(name, NOTRACK, 1));
	}

char *getenv(const char *name)
	{
	char **p;
	int namelen = strlen(name);

	if ((p = lookup(name, namelen)))
		return ((*p)+namelen+1);
	return (NULL);
	}

A POSIX Env-Game

So RC2014WW didn’t go so well… needing to provide a very few POSIX.1-2001 capabilities on Solaris 2.6, I got a bit distracted, aiming for a perfect implementation.

The problem is the multitude of incompatible de-jure APIs for manipulating environment-variables:

  • Direct access/manipulation of the global variable char **environ; and of manipulating the pointed-to pointers-to-strings, and the strings themselves.
  • Direct access/manipulation of the third argument to main(), in the same manners as above.
  • putenv(), the non-allocating environment-modifier inherited from the XPG4 standard; being non-allocating, you can directly alter the name and contents of the envrionment-variable after having set it – without calling any API function (ouch!) – by simply updating the pointed-to characters, eg:
    static char junk[128];  /* must be static! */
    strcpy(junk, "BIGGLES=scarf");
    putenv(junk);    /* env: BIGGLES=scarf */
    junk[2] = junk[3] = 'B';  /* now: BIBBLES=scarf */
    strcpy(&junk[9], "tupid");  /* now: BIBBLES=stupid */
    

    which is fairly braindamaged, and is explicitly allowed (described and documented) in the IEEE Std 1003.1-2001 API Specification (POSIX 1003.1-2001).

  • setenv()/unsetenv(), the more modern allocating/deallocating equivalents to putenv().

Because putenv() does not allocate, but can be used to delete or update variables allocated and set by setenv(), it doesn’t take a rocket-scientist to see that there be potential memory-leaks lurking there that are completely outside the control of the API-user, not to mention invalid deallocations when used the other way around.

Therefore putenv() needs to know about the internal implementation of setenv() and unsetenv(), and vice versa.

Thus, if your environment has putenv(), but not setenv(), you cannot just craft-up a setenv() function to fill the gap – you have to replace the existing putenv() as well.

Mixing Global Variables and API Functions – A Bad Idea
Although the structure of environment variable storage are exactly defined by POSIX, their storage-class is not – it is treated as an implementation characteristic. In other words, at program startup, the environment-variable array and pointed-to strings could be stored on the heap, the stack, the static data-section, in alternate-bank memory, or even in physically read-only memory. Of course, only one of those can be deallocated, so a portable implementation of unsetenv() cannot use free() on any of the initial environment strings, or on the global array-pointer either.

This in turn means that the first invocation of any one of the putenv(), setenv() or unsetenv() functions must transfer the strings and the global array of pointers into allocated heap memory, and update the global “environ” pointer, to be able to safely proceed.

Unfortunately, that update of the global array-pointer immediately invalidates the third argument to main() – thus the POSIX.1-2001 standard explicly notes that accessing the environment via the third argument to main() *after* any call to getenv()/putenv()/setenv()/unsetenv() will result in unspecified behaviour… possibly even a segmentation violation or arbitrary memory-corruption.

That’s useful…NOT!

putenv() Argument Validation – Cannot Be Bullet-Proof
As can be imagined, a putenv() implementation should ensure that the passed-in string is validated – it must contain an equals-sign. Unfortunately, because the programmer still has direct-access to the putenv-ed string, they can still corrupt the environment storage without any deviousness at all:

static char str[128];
strcpy(e, "LC_ALL,C");
if (putenv(e) < 0)  /* no equals sign, but catchable */
    exit(2);
strcpy(e, "LC_ALL=C");
if (putenv(e) < 0)          /* OK so far... */
    exit(3);
e[6] = '+';  /* we have now corrupted the storage 
              * by overwriting the equals sign... */

It is precisely for these reasons that setenv() takes separate arguments for the Name and Value of the environment-variable concered, and also why it always allocates the storage necessary for the resulting environment-variable string.

Finagling the Root Pointer
Of course, even if putenv() did not exist, the programmer could provoke similar problems by directly manipulating the pointed-to strings or the pointers via the global environ pointer – or even by changing the global environ pointer to point at a completely different array of pointers-to-strings. Jeepers!

static char *faked[] = {
      "ALPHA=first", "BETA=second", "GAMMA=third", NULL
      };
...
environ = faked;
setenv("ALPHA", "newval", 1);  /* must not try to deallocate
                                 the old value!!! */

To be able to catch this situation (and free the storage previously allocated via the old root pointer, to prevent a memory-leak), putenv(), setenv() and unsetenv() need to “remember” the previous value of the global root environ pointer, and if it changes, do the necessary cleanup. Of course, the program concerned could later change it back, but fortunately for implementors, that is expressly described as invoking “undefined behaviour” (ie: the program cannot rely on anything if it reverts the environ pointer after calling any of putenv(), setenv() or unsetenv().

Aside: An API-Users Perspective
Given the strangeness from mixing the different formally-standadised access/update mechanisms, one would expect that the programmers rule-of-thumb would be:

  1. If you have setenv()/unsetenv(), use them exclusively: do not call putenv(), and do not manipulate directly via the global env pointer or the third argument to main().
  2. If you do not have setenv()/unsetenv(), use putenv() but with great care: never update the characters pointed-to by the argument you passed to putenv().
  3. If you have neither putenv nor setenv(), write your own local setenv()/unsetenv() functions, as per the POSIX.1-2001 specification.
  4. In no case ever update the global environ pointer or any of the string-pointers in the array.

So what does Netrek server do? It uses both putenv() *and* setenv(). Not good – it means that I will have to provide the full leak-proof, pointer-spotting, shared-knowledge-implementation of putenv(), setenv() and unsetenv()…

Building the Netrek Server – autofake

Building the Netrek server software on Solaris 2.6 is problematic – due to the all-too-common misuse and failings of automake/configure, combined with some loose programming.

Running the ./configure script does not initially report any errors, but (it turns out) also does not adapt the build/software for some platforms that other automake-based software does…

Not Quite a Dependancy Problem
The Netrek vanilla servers’ configure invokes a subsidiary configure script in the res-rsa subdirectory, which initially whinged about not being able to find the GNU Multiple-Precision Math library (generally used for programmable-sized fixed-point math instead of floating-point, including BIGINT math which is just a special-case of fixed-point). And not just “GMP”, but “GMP 2.x” in particular (current version is GMP 5.x). The README therein points out that the old “GMP 1.x” is not suitable, that being based on the archaic original BSD MP API. Strangely enough, that old archaic BSD MP API was replaced with a revised API in (BSD 4.4) 1992 when it was recognised that the original conflicted in an awkward way with the ANSI C89 standard math library. Given that the old API has completely different function-names, the point is moot anyway – the configure script would not accidentally find and accept an old one even it it was there… this is exactly the kind of automake nonsense that gets on my nerves!

Of course, Solaris includes a “new-MP” API library built-in, but with the “new-BSD-compatible” name libmp and header “mp.h” instead of the GNUised names libgmp and “gmp.h”.

If In Doubt, Use Brute Force
To use the Solaris built-in MP library in place of the (not installed) GNU MP library, a couple of manouvers are needed. We need to create a “gmp.h” header file that simply references the “mp.h” header, and also post-process the res-rsa Makefile produced by the configure script to tell it the compiler options to locate and link with the “libmp” library. We do this using brute force, by creating a wrapper script “myconfigure”:

./configure \
        --sysconfdir=/etc/netrek \
        --sharedstatedir=/var/netrek \
        --localstatedir=/var/spool/netrek || exit 1
# create a usable "gmp.h" header:
echo '#include "mp.h"' > ./gmp.h
# Adjust the generated res-rsa Makefile to use our "gmp.h" header and
# to and link with "libmp":
sed     -e 's/GMP_INC =.*/GMP_INC = -I../' \
        -e 's/GMP_LIB =.*-lgmp/GMP_LIB = -lmp/' \
        -e 's/^\(all:.*\)nogmp/\1/' \
         res-rsa/Makefile > /tmp/$$ && cp /tmp/$$ res-rsa/Makefile || exit 1
rm -f /tmp/$$

So far so good.

socklen_t
The Netrek vanilla server is currently exclusively IPv4, it is not IPv6 capable – it uses the classic BSD-style socket functions such as gethostbyname(), and not the newer IP version-agnostic functions such as getaddrinfo(). However, in a couple of places it uses one of the newer typenames – socklen_t, which is unfortunate for systems that are not IPv6 capable or POSIX.1-2001 conformant. It could be argued that this is a case of erroneously mixing APIs…

Running make spits an error:

rsa_key.c: In function `decryptRSAPacket':
rsa_key.c:56: error: `socklen_t' undeclared (first use in this function)
rsa_key.c:56: error: (Each undeclared identifier is reported only once
rsa_key.c:56: error: for each function it appears in.)
rsa_key.c:56: error: syntax error before "addrlen"
rsa_key.c:68: error: `addrlen' undeclared (first use in this function)
gmake[1]: *** [rsa_key.o] Error 1

For the classic BSD-style (IPv4-only) socket API, in all the places where socklen_t would be used for IPv6, the appropriate type is int. Thus I thought that the obvious way to handle this would be to define a suitable CFLAGS environment-variable (to tell the compiler to treat all ocurrences of socklen_t as if they were int) before running ./configure (the typical “approved” method of handling this kind of thing with automake-based software):

CFLAGS=-Dsocklen_t=int  ./configure

That should do it!

The Subsidiary res-rsa Configure Script
Well, unfortunately not:

config.status: creating tools/admin/Makefile
config.status: creating include/config.h
=== configuring in res-rsa (/var/home/spoof/nt-server/netrek-server-vanilla-2.14.0/res-rsa)

configure: running /bin/ksh ./configure '--prefix=/usr/local/games/netrek-server -vanilla'  
 '--sysconfdir=/etc/netrek' '--sharedstatedir=/var/netrek'
 '--localstatedir=/var/spool/netrek' 'CFLAGS=-Dsocklen_t=int'
 --cache-file=/dev/null --srcdir=.

configure: warning: CFLAGS=-Dsocklen_t=int: invalid host type
configure: error: can only configure for one host and one target at a time
configure: error: ./configure failed for res-rsa

This cryptic message indicates that the subsidiary configure script in the res-rsa subdirectory was generated by a much older version of automake which does not understand the mechanism that the top-level configure script uses to pass overridden CC, CFLAGS, etc settings. Darn!

An Old Trick: Use a Custom C Compiler
Not being able to pre-define CC or CFLAGS, but needing to always invoke the C compiler with an additional flag, it was time to write a custom C compiler – in a manner of speaking. The trick is to create an executable script that invokes the C compiler *with the additional flag required*:

#!/bin/ksh
exec /usr/local/bin/gcc -Dsocklen_t=int "$@" 

Due to not being able to specify the CC environment-variable, this script must be called gcc, and must invoke the real gcc by absolute pathname; we also set PATH so that it finds this script in preference to the real gcc:

PATH="$HOME/bin:$PATH" ./myconfigure && gmake

Are We There Yet?
Still not quite: there remains a couple of missing functions at link-time:

Undefined                       first referenced
 symbol                             in file
setenv                              libnetrek.a(getpath.o)
round                               libnetrek.a(ping.o)
ld: fatal: Symbol referencing errors. No output written to ntserv

These are POSIX.1-2001 functions that are not present on Solaris 2.6. However, I have a plan… stay tuned!

Netrek – Ghostbusted?

Learning and Culture
Starting from the http://www.netrek.org site, I thought I should read-up on the basic gameplay and cultural issues for Internet Netrek games (watch out, there are some subtle things considered “bad form”). The game play is rather more complex than I had imagined, and the controls are understandably idosyncratic, but I guess I can learn it as I go along… There is a lot to read about the gameplay and team tactics before even starting, and a moderate amount of Netrek-specific “lingo” to learn – judging from the way the guides are written, clueless unacculturated newbies are not generally welcome.

Software Variations on a Theme
Hmmm… this Netrek lark isn’t quite as straightforward as I had anticipated, there are several modified game-server engines, and standard-protocol and protocol-enhanced clients…

Game/Server Types:

  • Bronco – standard Netrek game (original team gameplay and rules)
  • INL – stands for “International Netrek League”. It’s basically Bronco, but with team captains who can choose a scoring system and time limits. It’s used for clue games, team versus the world, and league games.
  • Base Practice – used to learn how to be a good starbase pilot. No rank is needed to get a base, and special robots mass-attack (“ogg”) the practice bases.
  • Sturgeon – explores several possible alterations to the game. Ships can use kills to purchase weapon upgrades. The powerful Galaxy Class (GA) ship is available.
  • Netrek Hockey – a cross between Netrek and, you guessed it, Ice Hockey. The planets are laid out to form a rectangular hockey-style rink, including goals. Ships use tractors and pressors to shove a puck around, fighting with phasers and torpedos as needed to get enemies out of the way.
  • Chaos – includes various modifications designed to make the game chaotic. Typically, this will include Galaxy Class ships (GA’s), a galaxy which wraps around, and 3 on 3 Team-Mode.
  • Paradise – involves extensions to the game system, so you need a Paradise client in order to play it; the usual clients won’t do. There is no Paradise client for Windows. Paradise has planets that move, suns, asteroid fields, eight additional ship types, missiles, and more.

Client Types:

There are also several different UNIX ports of the “standard” client software available: COW, BRM, BRMH, xNetrekM; and two variations on the “Paradise” extended-gameplay client software: TedTurner and Paradise 2000. Some of these vary in a few dimensions (stereo versus mono versus no sound, 256-color pixmap support, and so on and so on).

Fortunately, the situation isn’t anything like as bad as the multitude of completely incompatible (enhanced – NOT!) DOOM engines/clients. It looks like there are only two variations on the network protocol and client compatibility – “plain” and “Paradise”. So i’ll be going “plain”, of course – not going to mess with any new-fangled 15-year-old gameplay enhancement!

Download a Prebuilt Binary?

From the http://www.netrek.org nexus, there are links to the various client softwares for Windows 9x/2000/XP/Vista/etc, MacOS X, MacOS 9, Linux, OpenBSD (x86 and SPARC), FreeBSD, AIX, generic UNIX, and several others, both source and binaries. There is even a pre-built binary for SPARC/Solaris 2.6 – hey bingo, let’s give that a try!

Unfortunately, that prebuilt binary is for Solaris 2.6 with added self-compiled X.org software pre-installed (it is linked against libX11.so.6, not the stock Solaris 2.6 libX11.so.4). Thanks for nothing, chaps!

Why do that, when downloading the “COW” source tarball and running

./configure && make

builds a perfect fully working netrek client linked against stock Solaris 2.6 libs, with both the GNU C compiler or the Sun C Compiler? Sheesh…

Do it the Easy Way – Build From Source!

So I did the latter, and Hey Presto! a fully working netrek client executable for stock Solaris – it was the easiest install-from-source I have done in many a year (I could rant on and on about the SETI@home client’s totally farked autoconfiguration system, but… I suppose this is not the place…).

Where’s the Ejector Seat?

Unless otherwise directed, the netrek client inituially attaches to a couple of game meta-servers to discover which Internet games are currently active or waiting for players. So when I launched netrek the first time I was presented with a list of four (non-Paradise) games to join, and happily selected the one at the top… cut scene to the standard four-panel netrek screen and 20 seconds later the software reported that I had been ghostbusted

which means that the server has decided that you are gone, and frees up your player slot. If this happens, your client will try to recover; if it can’t, then the only thing to do is quit out and start up again. If you see a player get killed and not come back, but their slot is still up on the playerlist, they may be in the process of being ghostbusted.

However, I suspect that my 20 seconds of 99.9% inactivity had resulted in me being forcibly ejected from the game as a clueless newbie…

RC2014WW Project – The Game is Afoot

Today was fruitless, trying to find an affordable and usable donor PSU to build a new custom replacement PSU for a SPARCstation-10. Farnell, RS Components, Sparkle, http://www.minipc.de, picoPSU, eBay, Amazon, AliBaba, and hundreds of other places. No joy, either bad size, not able to source 21 Amps on +5V rail, require non-zero loads on 2nd +12V rail, or just too darned expensive.

So back to Plan B – to port the 1988 game NetTrek onto SPARC Solaris 2.6 and set up a multi-system multiplayer game (might need my partners’ help as the second player). According to Wikipedia, there are still a few NetTrek games being played on the Internet, so I will also try to find one and join in.

Of course, if time and opportunity permits, the SS10 PSU task might also feature…

Project Hunting

Ah, it’s that time of year again: I need a project for the RetroChallange 2014 Winter Warmup.

A few have asked for another whimsical project, and (for the sake of balance) a SPARC or Solaris-based one.
Unfortunately, I can’t think of anything quite as whimsical as last years effort, The Sound of Violence.

I had toyed with the Indigo PSU rescue project (Indigo Tendencies), but I am still stumped trying to find a suitable donor PSU to use due to the archaic DC power requirements of the SGI Iris Indigo.

On the SPARC front, my surviving SS10 PSU is starting to flake – it will run a pair of 133 MHz HyperSPARC CPUs perfectly fine for months on end, but as of June this year it will no longer run a pair of 180 MHz CPUs – it hard-hangs after a few minutes – and it is not the CPU modules that are at fault, as I have tried several modules and they all work in pairs in an SS20, and work individually in the SS10. The SS10 PSU seems to be getting a bit marginal, so the thought would be to replace it with an adapted modern higher-rated PSU (it needs to be a slim tiny unit, and need adaption, due to the very specific size and shape of the SS10 bay and the oddball fan arrangement there). I have identified current PSUs that would be usable – the TDK Lambda NV1-350TT-N3 or NV1-453TT-N3. For the SS10, the PSU needs to be able to provide 5V@21A, 12V@3A and -12V@0.1A, and *must* provide a 5V HCMOS “power-good” signal and a 5V@1A standby supply, and ideally also 5V HCMOS power-off and power-on lines.

The only problem is that although those TDK PSUs are currently available, they cost approx GBP140, which is way too much for a hobbyist project. Although Ebay and Amazon turn up a few NV1 units, none of them are the -N3 version, which is what is needed for the SS10.

OK, so what else is there? Perhaps building NetTrek game for Solaris and running a multi-player network game would be interesting – NetTrek is one of the earliest multi-computer multi-player networked games (1988), so I guess it would meet the “Retro” requirement…

The other possibility would be “SETI@again”: for three months in 1999, I had been running SETI@home on a network of 50 SPARCclassic (50MHz) machines and a Sun Enterprise 6000 (14 * 250MHz) in the off-hours overnight; until the network bandwidth required got a little too much to hide. The SPARCclassic machines took 10 days CPU time to process a single work-unit, so it was a good job there were 50 of them! The E6000 processed approx 25 work-units per day. I have recently been trying to build the new (since 2005) BOINC-based SETI@home software on the SS10 under Solaris 2.6, a difficult job due to the dreadful (broken) autoconfig scripts – works OK on Solaris 8 with GCC 4.x, but (in spite of the scripts claims) are no-go on Solaris 2.6. I have been trying to get this going all December, so perhaps the project could be to complete two SETI@home workunits on the SS10 by end of January?

Hmmm, decisions, decisions!