home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Amiga Elysian Archive
/
AmigaElysianArchive.iso
/
prog
/
c
/
ixemlib9.lha
/
README
< prev
next >
Wrap
Text File
|
1992-03-17
|
32KB
|
697 lines
Markus M. Wild March 16, 1992
This is the second public release of my shared library, that should make
porting programs originally written to be run on a **IX/BSD system to be
much easier on the Amiga computer running AmigaDOS.
---------------------------------------------------------------------------
COPYRIGHT restrictions
The source code shipped with this library is subject to the GNU LIBRARY
GENERAL PUBLIC LICENSE, please look at the file COPYING.LIB that comes with
this distribution. If not, write to the
Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
NOTE:
There are lots of files subject to other Copyrights, especially those
Copyright by the Regents of the University of California. In those cases,
the specified Copyright restriction applies TO EACH SUCH SINGLE FILE, but
not the arrangement of putting it in this library. With this restriction I
want to enforce consistency of this library.
After reading the COPYING.LIB file, you'll notice, that a program that just
uses the library by using OpenLibrary(), and calling functions in it, is to
be considered as a `work that uses the Library', and 5. of COPYING.LIB
says for this case: " Such a work, in isolation, is not a derivative work
of the Library, and therefore falls outside the scope of this License."
Since I declare the glue functions created by compiling and running
gen_glue.c (in lib/) to be in the Public Domain (thus not to be covered by
any license), your compiled and linked executable will NOT become a
derivative of the library, and will thus not be subject to this license.
Thus, you may use the compiled version of the glue files and the stdio
functions, libc.a (except alloca.c, please see the copyright notice in its
header. Use the builtin alloca() (__builtin_alloca() to be explicit) in
all situations where this is possible) and crt0.o in a commercial product
without making it a derivative of the library and thus make it subject to
the library license. However, you must tell your customers that
ixemul.library is free software according to this license, and where they
can get a copy of its source code.
---------------------------------------------------------------------------
Now that the legal stuff is over, some comments to this library.
*** note to upgraders (that is, people who already worked with the previous
*** versions): The following text is the (slightly edited) README file of
*** the previous version. If you decide to skip to the really new stuff, you
*** might miss important information of things that have changed, so if at
*** all possible read the first part as well!
68000 users, take care!
=======================
I tried to be fully 68000 compatible (this is important in the signal
code), but since I don't have a 68000 equipped Amiga anymore, I can't test
whether the library really works under this environents. Please tell me,
if you encounter problems! Since this is the second library release,
chances are high that most things should work, as I did extensive bugfixing
in this area over the past months. The warning still holds though, I just
*can't* test this compatibility issue myself!
I always wanted a library, that would emulate as much as possible of a
**IX/BSD environment on the Amiga, so that programs (usually programming
tools) written for **IX/BSD could be ported in a quick and straight forward
way to the Amiga. I guess the library accomplishes this goal fairly well.
What it is and what it isn't
============================
The design of the library was therefore guided towards **IX/BSD
compatibility, and *not* :
o to be too conservative with resources
o to be particularly conformant to Amiga habits. Thus if I had to decide
whether I should make a function act more like an Amiga function or
more like a **IX/BSD one, I decided for the latter. As an example:
_cli_parse() does wildcard expansion, and tries to apply more or less
**IX/BSD shell semantics to an argument line, it doesn't call
ReadArgs().
The types used in my own source code are all from sys/types.h (except
BPTR). I don't think capitalized identifiers should be used for typedef'd
types. According to C-conventions, anything written in captials should
be `#undef'inable, which typedefs aren't. Thus if you write contributions
to be included into the official distribution of this library, code
according to this. Use `u_char' and not UBYTE, etc. I don't care that
this is against the Commodore coding standard, this is my code, and
I decide what I like and what not.
o to be particularly suited for inclusion into a shared library, although
most things *are* shared now. What I'd really want for the Amiga is
the concept of a dynamic linker.
On the other hand, it should be:
o expandable. As an example, a file descriptor already can refer to `real'
files, directories, memory buffers treated as files. I plan to add
sockets in some next release (Commodore: please get out some examples
on how to use SANA-II stuff, so my sockets can be compatible!)
o patchable. If you want some function to behave differently, you can
SetFunction() it, and the rest of the library should use your new entry.
NOTE: I used this only for major functions, that may reasonably change.
I didn't call functions like strcmp(), strlen(), bcopy() that way for
efficiency reasons (and my lazyness to change the whole string/
and other libraries;-)))
This version doesn't particularly follow this goal very well, mostly
stdio is still the original BSD code, and doesn't use syscall()...
Difference to `usual' Amiga shared libraries
============================================
The library is designed to be used by C, and not by assembly. So
parameters are passed on the stack rather than in registers. This also
means that there is no `fd' file, and you can't use any current library
call pragmas to access its functions. Recall though, that calling
functions of ixemul.library inline will not result in an order of
improvement as calling standard library functions inline. The glue
functions don't have to shuffle arguments from and to the stack, they just
do a jump over the base table and are therefore very short and very fast.
I provided revision control in the startup code. Thus if you get a program
written for a newer revision of the library than you currently have, a
warning requester tells you about this, you can still use the program though.
I don't believe in bumping up the version counter because of every small
improvement. Bumping up the version means you can't use any older versions at
all. Bumping up the revision just gives you an annoying requester (and
perhaps an `illegal action' trap though..) that you should upgrade.
As some kind of replacement for an fd file I use the <sys/syscall.def> file,
which is a symbolic link to `library/syscall.def' in the source directory.
It contains lines of the form
SYSTEM_CALL (name_of_call, entry_number)
Where `name_of_call' is the name you'd use in a C program using the library
(for example write, abort, unlink, etc), and `entry_number' gives the number
of the system call used in syscall(). You can convert this number into
a more common _LVO for the Amiga by doing:
_LVO = -6*(entry_number + 4)
For examples how <sys/syscall.def> can be used, look at <sys/syscall.h>,
`library/start.s' and `lib/gen_glue.c'.
Where to use it
===============
Libc.a should be the one and only C library you need to get most of your
programs linked correctly with the GNU CC compiler. For some math-
oriented stuff you'll also need a math library, libm.a (the BSD math
library) should be suited fine for this. Just link with `-lm'.
If you want to recompile
========================
I provided Makefiles for the most directories (for all by now ?). Those
Makefiles assume a decent Make tool, I'm currently using a version (hacked
for the Amiga) of DMake (not the one from Matt Dillon, sources are on
wuarchive.wustl.edu), you'll have to rewrite the Makefiles if you don't use
DMake, as the .LIBRARY feature seems to be unique, other `make's use other
techniques to accomplish the same goal, if they support it at all.
Sources are written for compilation by GCC. You might be able to compile
95% of them with any ordinary ANSI-C compiler, but there are cases where
you have to change things for non-GCC environments (mostly asm() situations).
For recompilation, see also the following comments in `How complete are the
headers'.
To regenerate libc.a, cd into lib/ and type `make'. Then cd into `glue',
enter `/gen_glue', and wait for it to finish (can take quite some time, it
has to create more than 400 files !). Then `make' in this directory and cd
back to lib/. You may now (cross your fingers and) type `make libc.a', and
if you have sksh configured as system shell, libc.a should now be built
automatically. If something doesn't work, look at the Makefile and do it
manually ;-)
As an explanation to the separate compilation of crt0.o: this has to be
compiled with `-fwritable-strings', since you have to be sure that ENTRY()
is the first thing in the generated object file. If you don't specify
`-fwritable-strings', you'll get string constants at the first executable
address in your programs, and this will get you (and your computer) into
meditation if you try to execute such programs ;-))
To regenerate ixemul.library, cd into string/, gen_library/, stdlib/,
gnulib/, gnulib20/, stdio_2/ and at last library/, and type `make'. At the
end, you should get a current version of ixemul.library.
Note: the library is currently compiled in a way that makes it easy for me
to have a debugged version or not, ie. the debugging statements stay in
the code, but the kprintf() function is either linked with the library
to get a debugging version, or stubbed out to get a working version. When the
library gets more stable, parts of it can be compiled by inlining code
to the library, instead of going thru syscall().
If you want to write code that uses the library
===============================================
Code like you would on a **IX/BSD system, thus DON'T USE any information
private to the library, especially don't use any information that
tc_TrapData points to. This data is subject to change in every release of
the library, and you may only access its variables thru library access
functions! This restriction fully applies as well to applications that
SetFunction() some functions of the library. So if you for example write
your own memory allocation functions, you may NOT use the space the library
malloc() function uses for your own purpose. I may decide in a later
release of the library to use a different malloc() implementation that
uses different data in the user area, and then your code almost certainly
would trash innocent variables!
How complete are the headers
============================
The header files distributed in `include/' are all you need - except the
Amiga specific header files copyright by Commodore-Amiga. You either have
to get them from a commercial compiler, or order them from CATS.
If you don't intend to compile Amiga specific programs, you don't need
those headers at all.
You need to make one change to one of those Amiga headers to avoid
duplicate definition of a datatype:
The <devices/timer.h> file includes the following definition:
struct timeval {
ULONG tv_secs;
ULONG tv_micro;
};
Please comment this definition out, and add
#include <sys/time.h>
somewhere at the beginning of the file, and instead of the above definition,
put
#define tv_secs tv_sec
#define tv_micro tv_usec
That way, you can use the timeval structure defined in <sys/time.h> as well
as the one defined in <devices/timer.h>. The structures are identical, but
the field names are not (sigh..).
I included more or less all headers from 4.3BSD-net2, except those that
refer to really **IX/BSD specific material in the kernel. I included more
headers that are currently used and supported, just to make life easier for
people using **IX/BSD sources under AmigaDOS. Among the things not supported
currently are networking (sockets) and process management (fork, eg).
Signals on the other hand *are* implemented, see the special section for
restrictions.
How BSD signals are implemented
===============================
I tried to implement as much of Berkeley style signals as possible on the
Amiga. This includes a trap handler as well as an asynchronous signal
facility. The one thing not implemented are interruptable system calls.
Since there are no `real' system calls on the Amiga (ie. no calls that are
executed in Supervisor mode), those calls cannot normally be interrupted,
ie. forced to return to their caller. So all functions except sigpause()/
sigsuspend() will return to where they were interrupted if a signal
occurs.
These 32 new signals are 32 really new signals, not tied to any of the 32
Amiga signals provided by Exec. The one exception is SIGBREAKB_CTRL_C,
which is by default bound to generate a SIGINT.
Signal handlers are called with the following arguments:
void
signal_handler (int signo, int code, void *address, struct sigcontext *sc)
Where
signo: is the signal number that occured, see <signal.h>
code: is a more specific characterization of signo available with some
signals. It is available with all signals that are generated
because of a processor exception, and then contains the format
identifier of the exception frame (this is correct even for the
68000, where such an identifier is faked, ie. it doesn't really
exist). Thus a `division by zero' exception would be invoked by
signal_handler (SIGFPE, 0x2014, address, sc)
address:address referrs to the instruction that caused the signal.
sc: please don't use sc, as it may change in the future. It contains
the context to restore after the signal handler returns.
***************************************************************************
If you use signals in your own code, make sure that you never allow a
situation, where when your program is interrupted resources stay allocated!
***************************************************************************
That is, the following example is BAD :
..
fh = Open ("foobar", MODE_OLDFILE);
if (fh)
{
.. do something with it ..
Close (fh);
}
If your program is interrupted and terminated after you got your file handle,
`fh' will never be closed! There are two sollution to get around this problem,
either use library functions from ixemul.library, or explicitly mask signals
while you have resources locked. Thus in this example, either do:
fd = open ("foobar", O_RDWR);
if (fd >= 0)
{
.. do something with it ..
close (fd);
}
in that case the library will do resource tracking on fd. Or explicitly mask
the signals:
omask = sigsetmask (~0); /* mask all signals */
fh = Open ("foobar", MODE_OLDFILE);
if (fh)
{
.. do something with it ..
Close (fh);
}
sigsetmask (omask); /* reset the mask */
Note that the second sollution is worse than the first one, because the
user may send the process a non-maskable signal that would terminate the
process unconditionally (SIGKILL does this), and don't forget that the user
isn't able to break your program as long as you have signals masked!
Ixemul.library does resource tracking on all file-related functions (create(),
open(), dup(), pipe()) and on memory allocations thru malloc() and realloc().
Thus if you use those functions instead of dos.library and exec.library
functions, you don't need any clever resource tracking stuff to do on your
own, that's what the library is for ;-)
If you use Amiga specific resources like Windows and Screens from
Intuition, make sure to add an atexit() handler to close those resources,
if the user should decide to interrupt your program. Before the program is
left, the chain of registered atexit-handlers is called in exit(). So
PLEASE NEVER EVER call _exit() if you have registered any custom atexit()
handlers. It is a bad habbit anyway, but normally you may call _exit()
without resource lossage (stdio won't flush its buffers, but that's about
all), as long as you close ixemul.library after use, and this IS A MUST, as
for every Amiga shared library anyway.
I provided a new unique Amiga specific signal called SIGMSG. If you set up
a handler for this signal, then
o the default mapping from SIGBREAKB_CTRL_C into SIGINT will no longer
occur
o your handler is called with the following arguments
signal_handler (SIGMSG, new_exec_signal_mask)
In this case, you have to deal with Exec signals yourself, so don't forget
to clear those signals that you want to receive notification about again
later.
Thus if you'd want to handle SIGBREAKB_CTRL_C yourself, don't forget to
SetSignal (0, SIGBREAKF_CTRL_C)
at the end of the handler, or you'll never get notification about that
signal again.
If your program is interrupted by a signal and the default action of that
signal is to terminate your program, and you didn't set up a handler to deal
with that signal, your program is terminated by calling `exit (128 + signo)'.
There are no core-dumps yet, I first have to think about a useful format
for a debugger that takes care of the Amiga's memory architecture.
The signal implementation uses some of the Berkeley kernel sources of the
4.3BSD-reno release for the hp300. I didn't disable everything that isn't
implemented currently, so you might face strange behavior if you currently
try to send a SIGSTOP to a process using the library, you better not ;-))
Currently supported are the following signals:
SIGINT: bound to ^C (SIGBREAKB_CTRL_C) unless there is a SIGMSG handler
SIGILL: generated by some hardware exceptions
SIGFPE: generated by some hardware exceptions
SIGBUS: generated by some hardware exceptions
SIGALRM: if you use alarm() or the ITIMER_REAL interval timer
SIGVTALRM: if you use the ITIMER_VIRTUAL interval timer
SIGPROF: if you use the ITIMER_PROF interval timer
SIGMSG: if you provide a signal handler for it
SIGCHLD: a vfork()'d child died (or stopped ?;-))
SIGSEGV: the programs used more stack than you allowed it to (see below)
more are to follow. You may send any of the 32 signals to a process using
the library with the `kill ()' function, the default behavior of a process
is described in a **IX/BSD man page for signals. As mentioned above,
stopping a process isn't currently implemented, and may produce strange
behavior...
Compatibility
=============
I tried to port some commonly used programs to the Amiga using this
library. And the following programs were quite easy to port:
o patch
o GNU tar-1.10 (the first Amiga tar that knows about symlinks ;-))
o GNU find-2.2 (replace the fork,exec-stuff with a call to ssystem() )
o BSD ar, ranlib (with DMake even things like VPATH=../ar work ;-))
o GNU cc ;-))
o DMake
o ...
As a guideline, if you find stuff that uses fork,exec,wait stuff, try to
replace it with a call to ssystem()/system(). system() corresponds to the
usual **IX/BSD library function, it runs the argument thru the current
shell. ssystem() is some lower level execution function, that under 1.3
uses ARPs SyncRun() function (that's where the name came from ;-)), and
under 2.0 uses my own code to find the executable (searches the users
PATH), and tries to do interpreter expansion on the file (the thing with #!
rsp. ;! as the first two characters. Please see the `library/__load_seg.c'
file for more details;-)).
Note for second version: beginning support for vfork(), exec*() and wait()
is provided, please use with care!
***
*** New stuff as of March 16, 1992
***
***************************************************************************
The library got quite a bit larger, and executables using it get smaller
and smaller ;-) This is mainly due to the fact that now stdio is included
in the shared library, and no longer in the statically linked libc.a. Since
I switched to the newest BSD stdio implementation, compatibility to older
versions was no longer required, and you get additional functionality in
the same run (setvbuf() for example). There is one compatibility problem
though:
*************************************************************************
Don't use the 1.40 libg++.a together with this new library, 1.40 libg++.a
has intimate knowledge of the old stdio implementation, and won't work
with this one (you'll probably get linktime errors, missing _iob). Use
the 2.0 libg++.a instead (soon on amiga.physik.unizh.ch as well).
*************************************************************************
The main new thing is `ixconfig', which you find in the bin/ directory.
ixconfig
========
ixconfig is used to tailor the library to your requirements and/or
habits. Just running ixconfig without options prints the current settings,
which look like this by default:
1> ixconfig
Translate . and .., translate /, don't translate symlinks,
allow AmigaDOS notation, membuf size = 0,
red zone size = 0, stack watcher is disabled (and not active).
Here's an explanation of those settings:
"translate . and .." mapping of `a/./b/../c' into `a/b//c' is enabled
"translate /" mapping of `a///b' into `a/b' and `/device' into
`device:' is enabled. Note: You can't currently get
a directory of the virtual `/' directory this way.
"translate symlinks" apply `translate /' to contents of symlinks as well
"AmigaDOS notation" allow use of device names in the colon form
(ie. sys: instead of /sys), and don't force `..'
notation.
"membuf size" if you set a non-zero value here, all files upto
that value, that are opened O_RDONLY are read
into memory, and read/seek operations occur in memory.
"red zone size N" size of `safety net'. If your program uses
so much stack, that the stack pointer is more
than N bytes near the stack bottom, your program
is sent a SIGSEGV signal. Red zone size is used
when starting a new process, if you change it later,
no already running processes are affected.
"stack watcher" global toggle. If disabled, no SIGSEGV signal is
sent to any program (but if red zone size is > 0,
the process keeps a pointer, so that if you reenable
the stack watcher, SIGSEGV will be sent again).
This was an explanation of the output of ixconfig, to change those values
type `ixconfig -h' for an explanation on the available switches. One
switch might need further explanation: `-s'. If you specify `-s', ixconfig
goes to sleep after setting the new parameters, and won't return until you
break it with ^C. This is the preferred switch if you run ixconfig from your
startup-sequence in the background, as then your changes can't be undone by
flushing the library (ixconfig keeps it open, so that Expunge() can't flush
it).
Some notes to vfork() and friends
=================================
**IX/BSD process management is one of the most nasty design differences
between AmigaDOS ans **IX/BSD. `fork()' for example is hardly possible to
implement on AmigaDOS, as it requires to create an identical copy of the
parent process. This is only feasible with virtual memory, where processes
can be mapped at equal places in memory. Under AmigaDOS this would have
to be simulated by copying of stack and malloc'd data whenever a process
is activated, and copying them to a safe place before it is disactivated.
This problem can be avoided, if the program to be run under AmigaDOS is
only fork()ing, because it just wants to start another process. In that
case, no such copying as described before is necessary, and BSD therefore
invented the `vfork()' function, which works like `fork', but runs the
child on the parents memory segments (stack and malloc'd data). While the
child is using the parents resources, the parent is sleeping in a not
interruptible state.
That much for theory;-) I tried to implement an as compatible as possible
vfork() function, that behaves like the BSD one. This should work under
any OS version, for Kickstart 1.3 the arp.library is used, starting with
OS 2.0 dos.library is powerful enough to do it itself.
Since I won't try to implement `fork', I provided a possible alternative
(you tell me;-)). As an extension, you get the `vfork_resume()' function,
which causes the parent to resume, just like it would if you called
`_exit()' or one of the `exec*()' functions. Since this function is quite
dangerous (and an even bigger hack than vfork() itself..), here's what's
happening in `vfork_resume()':
o the child switches to its own stack. After vfork(), the child is
using the stack of the parent process. Since no two processes can
share the same stack in parallel, vfork_resume() causes a switch
to the `real' stack of the child.
o the parent is sent a wakeup message.
o both processes run concurrently
The first point is the most important one: Since vfork_resume() changes
the stack pointer of the running process, you can't refer to any variables
or parameters anymore after calling vfork_resume()! Only register
variables survive such a call, and you have to explicitly store values
in register variables that are subject to survive!
There's another potential problem with vfork_resume():
**************************************************************************
Don't exit() from the parent before all vfork()'d children have died!!
**************************************************************************
Since exiting from the parent causes the parents code and data segments to
be deallocated, the child would find itself without code space to run
on, and would probably cause a severe machine crash!
So always call at least `wait (0)' before returning from the parent.
exec*()
=======
In most cases, you just use `vfork()' to later overlay the process with
a new image, that is you want to start another program. The way AmigaDOS
loads processes is not too well suited to do `exec' style program starting,
yet it is possible, although with slight resource wasting..
First problem is, that all exec* functions pass an argument vector to
the new program, whereas AmigaDOS programs expect to be passed an argument
line (instead of the vector of arguments). Since in my opinion it would
be a good thing if a program could get an argument vector directly (in that
case the inherent problem of passing multi word arguments to a program would
be finally solved, no more weird quoting needed!). That's why I provided
a mechanism that allows this vector passing, and it works like this (look
at crt0.c for a concrete implementation of this concept!):
The program has to provide a magic header at the first executable location
in its code. This magic header looks like this:
o JMP instruction to common AmigaDOS startup
o struct exec area. Use the OMAGIC a_magic code.
o provide an alternate entry vector in a_entry. execve() jumps thru
this vector to pass vectors to your program, instead of going
thru the normal AmigaDOS startup part.
As long as you use my crt0.o and libc.a, this whole thing is completely
transparent to your program, you only have to care for it, if you want
to support the mechanism in other languages as well.
The second problem is how to start `old' AmigaDOS programs from execve().
If the program has the described magic header, starting is easy. Else
another approach is taken, depending on the OS version. Common to both
OS versions (1.3 and 2.0) is redirection of stdin and stdout. Since the
new program can't refer to the real file descriptors (I can't pass the
open library without my startup code), I have to setup DOS fields to
use my filehandles. This may succeed or not, depending on whether the
descriptors in question are realized by DOS files or not (in the future
a not-compatible alternative would be descriptors that refer to sockets!).
Actual starting of the program is done with RunCommand() under 2.0, and
some own hack under 1.3. If someone is interested to get this working
well under 1.3, I'd be happy to include a better starter function, my
kludge doesn't particularly deal graceful with BCPL functions...
Documentation to the provided functions
=======================================
I'm a rather lazy guy, and I really hate it writing documentation ;-)
However, since the library is aimed at emulating as much of BSD as I could
find (more to come ;-)), you can use BSD man pages in almost any situation.
Such man pages can be ftp'd for example from wuarchive.wustl.edu, in
unix/4.3bsd-reno/lib/libc. Look for */*.[1-9] files in that directory
(a nicer way to obtain that information is to download the ls-lR file
from the root, and than to use grep on your local machine!).
Quick guide to those that just want to use the library with GCC
===============================================================
If you're not particularly interested in how things are implemented,
and just want an ANSI compliant C library with gcc, here's what you
have to do.
o unpack the archive (you probably did already ;-))
o you may delete the following directories, if you don't plan to
look at the implementation:
- gnulib/
- gnulib20/
- string/
- stdlib/
- gen_library/
- static_library/
- library/ (but first copy library/syscall.def into
include/sys/syscall.def, overwriting the symbolic link there!!!)
- libm/
o assign gcc: to the base directory, so that you can refer to the
include/ directory by `gcc:include', and to lib/ by `gcc:lib'.
o copy libs/ixemul.library into your system libs: directory, or add
the libs/ directory to LIBS: (only OS 2.0, creating a multi-assign for
LIBS:).
That's it, gcc should now work fine with this library!
What about inline/ headers for 2.0 functions ?
==============================================
I don't use 2.0 functions too often, and I didn't want to distribute
header files I rarely use. The basic step to generate those files
automatically involves parsing of prototype files and fd files, and
mixing the information together to generate a file of inline function
definitions. Since my current parser is ways too hacky that I would ever
distribute it, if someone else really wants inline headers supporting
2.0, write the necesssary tool yourself! You might want to contact me
before starting work, so that not multiple people make the same effort
at once.
Some final words
================
I wish you good luck using this library, it isn't that thoroughly tested
yet, but I did manage to recompile gcc with itself using the library, so
some basic reliability should be granted. But keep in mind:
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
New versions of the library will first be released on amiga.physik.unizh.ch
in amiga/new, migrating to amiga/devel later.
If you like to contribute new functions to the library, please reread
the `What it is and what it isn't' section, and if you think that your
code contributes to those goals, I'd be very happy to include it in
the library in a later release.
Since indentation style is a great deal a thing of personal taste,
I make up the following rules:
o if you change one of the existing files, follow the style of the
file
o if you provide a completely new set of functions, you're at your own.
If you find bugs in the code (I'm absolutely sure there are some...), please
tell me about them!
I'd like to thank specially Michael B. Smith and Michael Bond, who
contributed many bug reports and ideas for improvements, as well as all
the other bug reporters, thank you!
Send your bug reports, enhancement requests and constructive remarks to
<wild@nessie.cs.id.ethz.ch> or <wild@amiga.physik.unizh.ch>
send flames to <bitbucket@nessie.cs.id.ethz.ch> (but don't overflood
nessie's /dev/null please ;-)).
Markus M. Wild