Pro PDF
Pro PDF
Programming Guide
AMOEBA
Amoeba is a registered trademark of the Vrije Universiteit in some countries.
AMOEBA
2.2. Capabilities 2
2.6. Scheduling 5
2.7. Signals 6
2.9. Languages 7
2.9.1. Programming in C 7
3.1. Servers 11
3.2. Clients 17
6.1.1. Overview 68
6.3.1. Introduction 83
6.3.3. Overview 84
6.3.4. Example 84
6.3.7. Clusters 87
iv Amoeba 5.3
6.3.10. Expressions 94
Amoeba 5.3 v
vi Amoeba 5.3
1 About This Manual
Intended Audience
This manual is intended for use by people writing programs for Amoeba. It is assumed that
the reader is familiar with the introductory material in the User Guide.
Scope
This manual explains the basic concepts of Amoeba from a programmer’s perspective, the
paradigms, the programming tools, writing clients and servers and using the various module
interfaces. The manual also provides a description of the various library modules and
routines available under Amoeba.
There is a full index at the end of the manual providing a permuted cross-reference to all the
material. It gives references to both the manual pages and the introductory material which
should direct you to enough information to clarify any difficulties in understanding.
Additional material may be required for a complete understanding of the programming
environment. In particular the ANSI C and POSIX 1003.1 standards and some knowledge of
UNIX are helpful.
Warnings
As stated in the release notes, there have been several major changes between Amoeba 4 and
Amoeba 5. More are expected in Amoeba 6. It is important that the guidelines for software
development be followed to avoid upgrade difficulties. In particular, it is vital that all remote
procedure calls be encapsulated in stub routines to isolate the rest of the code from the
possible syntactic and semantic changes to this interface.
Amoeba 5.3 1
2 Paradigms and Implementations
2.1. The Client-Server Model
The programming model of Amoeba is based on having programs, possibly running on
different processors, working together to perform a task. This is inherent in the nature of
distributed systems. The programs communicate with each other using various methods, but
typically remote procedure call (RPC). RPCs may go over a network (typically Ethernet) or
to another process on the same computer. The location of the sender and receiver of a
message is invisible to the sender and receiver. Various functions can be implemented in
programs that offer a service. For example, the file system provides a file creation and
storage service for other programs. It can accept requests to create, read and write files.
Such programs which accept requests to perform operations for another program are called
servers . Programs which send requests to servers are known as clients. Some programs may
at various times in their execution switch between being a client and a server and so the
categorization cannot always be strictly applied to a particular program. For example, a file
server may become a client of the time−of−day server when it needs to obtain the time for a
time-stamp on a file.
Many standard servers are provided with Amoeba and so applications can usually be written
as clients of one or more of the standard services. However it is also possible to write
specialized servers to manage a data base or some other kind of object.
Amoeba is an object-based system. (N.B. Class inheritance is not enforced . It is permitted.
Therefore Amoeba is not an object-oriented system but it is relatively simple to build an
object-oriented system on top of Amoeba.) In general, each server manages a single type of
object. For example, a file server manages files and a disk server manages disks. It is
possible to write servers that manage several types of objects simultaneously but this can
easily lead to inelegant and difficult to maintain programs. Extensions are planned to give
proper support for this to avoid the inelegancies (see below).
2.2. Capabilities
When an object is created, the server that does the creation also creates a capability for the
object and gives it to the client. Access to objects is exclusively via capabilities. In principle
programs should never directly examine the contents of capabilities. Servers sometimes need
certain data from a capability, such as the object number or the rights. This information
should be extracted using the specially provided routines (see prv(L)). Similarly, generation
of capabilities for new objects should be done with these routines and the function
uniqport(L). Any programs not conforming to this may cease to function correctly in later
releases of Amoeba since in a subsequent release the sizes of the fields in a capability may
change.
The present structure of a capability is shown in figure 2.1. It is 128 bits long and contains
four fields. The first field is the server port, and is used to identify the (server) process that
manages the object. It is in effect a 48-bit random number chosen by the server.
The second field is the object number , which is used by the server to identify which of its
objects is being addressed. Together, the server port and object number uniquely identify the
object on which the operation is to be performed.
The third field is the rights field, which contains a bit map telling which operations the
2 Amoeba 5.3
48 24 8 48
Server Object Rts Check
port number field
Fig. 2.1. A capability. The numbers give the current sizes in bits.
holder of the capability may perform. If all the bits are 1s, all operations are allowed.
However, if some of the bits are 0s, the holder of the capability may not perform the
corresponding operations.
To prevent users from just turning all the 0 bits in the rights field into 1 bits, a cryptographic
protection scheme is used. When a server is asked to create an object, it picks an available
slot in its internal tables and puts the information about the object in there along with a newly
generated 48-bit random number. The index into the table is put into the object number field
of the capability, the rights bits are all set to 1, and the check field is generated by XOR-ing
the rights field with the random number and running the result through a (publicly known)
one-way function . The encryption process is built into the routines prv encode and
prv decode (see prv(L)). (N.B. For efficiency, the one-way function is not applied when all
the rights are ‘‘on’’ since there is no gain in security since all rights are available to the
holder of the capability anyway.)
The server can construct a new capability with a subset of the rights by turning off some of
the rights bits before XOR-ing the rights field with the random number. Each server should
provide a rights-restriction service for clients.
When a capability arrives at a server, the server uses the object field to index into its tables to
locate the information about the object. It performs an XOR of the original random number
in its table with the rights field of the capability. This number is then run through the one-
way function. If the output of the one-way function agrees with the contents of the check
field, the capability is deemed valid, and the requested operation is performed if its rights bit
is set to 1. Due to the fact that the one-way function cannot be inverted, it is extremely
improbable that a user can ‘‘decrypt’’ a capability to get the original random number in order
to generate a false capability with more rights.
Amoeba 5.3 3
N.B. getreq and putrep must always come in balanced pairs. If a thread attempts to do a
putrep when it has not done a getreq or does two getreq calls without an intervening putrep
(or grp forward) then this constitutes a programming error and the program will terminate
with an uncatchable exception.
The trans function is used by clients to send requests to servers. The port of the server to
which the RPC must go is embedded in the first parameter to trans. The trans call blocks
until the server sends a reply. The Amoeba kernel on which the trans is executed attempts to
locate the server by broadcasting a message with that port in it (unless it is on the same host).
If another kernel has a server waiting for a request on that port it responds and the RPC is
sent to the server which then handles the request and returns a reply. The kernel keeps a
cache of known locations of ports to improve performance for subsequent RPCs. However it
is recommended that trans not be called directly in client programs. Rather it should be
embedded in a procedure call that handles the marshaling of data and the sending of the
message to the server. This shall be described in more detail below.
Note that giving a null port (i.e., all bits 0) to any of the RPC routines will result in an RPC
error. The null port is not a valid address for a server. The routine uniqport(L) which
generates ports will not produce the null port.
The timeout function is used to set the amount of time spent searching for the server when
doing a transaction. This is known as the locate timeout . If a server is unavailable (possibly
due to network partitioning or a system crash) then programs attempting to communicate
with that server will hang for the length of the timeout. The default timeout is 5 seconds.
Such a delay will be very irritating to users so it is important in interactive programs that
timeouts be set to a value short enough to avoid irritating the user but long enough to have a
chance to find the server. Two seconds is the recommended minimum for interactive
programs. Any shorter than this may report failure to find the server even though it is
available. For non-interactive programs such as compilers, a longer timeout is usually
acceptable and increases reliability.
4 Amoeba 5.3
about how to use AIL in the programming tools section of this manual and in the section on
writing clients and servers.
2.6. Scheduling
At any instant many threads and processes may be runnable so a scheduler is needed to
determine which to run next. The scheduler uses a priority scheme as follows:
Enqueued interrupt handlers in the kernel have the highest priority, then runnable kernel
threads and finally, if none of the above are runnable, user processes.
Processes are scheduled round-robin on a time-slice (typically 10 milliseconds) or when a
Amoeba 5.3 5
process blocks. Thus between processes there is preemptive scheduling. However, between
individual threads within a process there is per default no preemption. Thus, if a process is
rescheduled due to a time-slice then when it is restarted the same thread of that process will
be resumed. Rescheduling between threads only occurs when a thread executes a blocking
primitive. This includes the transaction primitives trans and getreq (see rpc(L)) and the
mutex primitives (see mutex (L)). There is also a special function threadswitch (see
thread scheduling (L)) which allows a process to reschedule itself without executing a
function which blocks. The kernel is regarded as a process, so there is no preemption
between kernel threads. They are only rescheduled when they block.
It is also possible to allow a user process to use preemptive scheduling between its threads.
This is done on a per process basis so that not all processes have to use preemptive
scheduling. Threads can also be assigned priorities and there is time-slicing between threads
in this case. See thread scheduling (L) for details.
2.7. Signals
Signals are a method of sending unsolicited information to a thread or process. Lightweight
signals are sent to threads. The manual page signals(L) explains how they are implemented
and used. Heavyweight signals are used to stun a process (not a thread). This is described in
manual page for pro stun (see process (L)). It is important to note that if a thread is in a
transaction the signal handler of the thread will not be called until the transaction completes.
The signal will be propagated to the server which may either ignore it or stop what it is doing
and reply at once. Thus, when a user interrupts a process with CTRL−C, it may not die
immediately, but hang until the server replies to the current transaction.
There is a special kind of stun which allows a trans to be broken off without waiting for the
server to reply. This is also described in process (L).
6 Amoeba 5.3
a new process more efficiently than with fork and exec.
See posix(L) for further details of POSIX conformance.
2.9. Languages
Currently several programming languages are provided with Amoeba. The Amsterdam
Compiler Kit (ACK) provides STD C, Pascal, BASIC, FORTRAN 77 and Modula-2. The
standard run-time libraries are available for these languages (see libmod2(L) and libpc(L)).
However, the Amoeba library routines have only been provided for C at present. Note that
the ACK loader is capable of linking the routines from the C libraries with Modula-2
programs. See libmod2(L) for details.
The stub generator ail(U) only provides the possibility of generating stubs in C.
A special language for parallel programming has been developed. It is called Orca . It can
be obtained from the Vrije Universiteit.
The GNU C compiler is available for all architectures under Amoeba.
For further details on programming languages see chapter 5, Programming Languages.
2.9.1. Programming in C
The ACK C compiler distributed with Amoeba is a STD C compiler. The Amoeba source
code was written in K&R C but function prototypes and new−style declarations are gradually
replacing the old-style code. The STD C compiler can be used to compile the system. The
library routines and include files required by STD C are provided.
To compile a program under Amoeba it is sufficient to call cc(U). This will automatically
link programs with the correct run-time start-off and the relevant libraries. The two libraries
that are of importance are libamoeba.a and libajax.a . The latter is only required when using
functions that interact with the session server. (That is, when a program will use shared file
descriptors.) If linking programs without using cc then libajax.a should be linked before
libamoeba.a , which should always be the last library linked.
Note that just calling cc will result in compiling a program for the default architecture. It is
better to specify the target architecture if there is any doubt or if cross-compiling for another
architecture (see the −m option of ack(U) for details).
If developing a software system it is recommended that amake (U) be used instead of
make (U). Amake is more reliable under Amoeba (it does not use the unreliable time-stamps
on directory entries) and provides much more flexibility. There are many examples of
Amakefiles in the Amoeba distribution.
Amoeba 5.3 7
2.9.2. Standard Types
There are many standard types defined using typedef . The most important ones are defined
in the include file amoeba.h . These include the definitions of capability, port, header and
errstat. For the sake of portability it is important that these types be used uniformly
throughout applications. In subsequent releases the actual underlying types will almost
certainly change. For example, bufsize is presently an unsigned 16-bit integer. It is expected
that this will be upgraded to a signed 32-bit integer in a future release.
Since many types are used it is worthwhile for the programmer to become acquainted with
them.
In addition to the type definitions there are several macros defined which relate to the types.
It is worthwhile becoming acquainted with these as well. Two macros are of special
importance. The macro PORTCMP compares two ports. It returns 1 if they are identical and
0 otherwise. Similarly the macro NULLPORT returns 1 if the given port is null and zero
otherwise. An example of their use is given below.
#include "amoeba.h"
port p1;
port p2;
if (PORTCMP(&p1, &p2))
printf("p1 and p2 are equal\n");
if (NULLPORT(&p1))
printf("p1 is the null port!\n");
8 Amoeba 5.3
(Those that are not applicable should be ignored and a good status returned.) Furthermore
there are reserved command codes for standard servers defined in the include file cmdreg.h .
At the end of this file is the special symbol UNREGISTERED FIRST COM which defines
where local server’s command codes should begin. The reason for this division of command
codes is to allow the possibility of inheriting interfaces in an object-oriented fashion. AIL is
able to take advantage of this when building server interfaces. (See the Programming tools
section for details of AIL class inheritance.) Similarly, if a server needs to use non-standard
error codes, they should also be numbered from UNREGISTERED FIRST ERR.
Amoeba 5.3 9
3 Writing Servers and Clients
Servers are used to perform specialized functions and form the basis of centralized object
management. There are many standard servers delivered with Amoeba. These include the
Bullet File Server, the Soap Directory Server, the Virtual Disk Server and the Run Server
(which does load balancing at process creation-time), among many others. To develop basic
applications and port utilities from other operating systems the standard servers are usually
adequate. However for some applications this is not enough. This section gives an
indication of how to write your own servers and clients and points out some of the pitfalls to
avoid.
Servers normally have a fixed set of commands that they accept. They sit in a loop accepting
commands, executing them and sending a reply. To simplify the client’s interaction with a
server it calls a procedure, known as a stub. This packages the command and data in the
manner required by the server and sends it to the server using a remote procedure call (RPC)
and then awaits a reply. The procedure also unpacks any data in the reply and hands it back
to the client. The stub allows details of the server interface and the possible byte-order
differences between client and server’s processors to be hidden from the programmer. Below
is a description of how to write servers, their stubs and the clients that use them.
The following description of how to write clients and servers begins by showing what the C
code actually looks like for a client / server interface. The reason for this is purely
educational. In fact it is seldom necessary to write the server loop and client stub code
yourself. AIL (the Amoeba Interface Language) can be used to generate this code
automatically. The implementation of the commands must be written by the programmer of
course. One advantage of this is that if the RPC interface ever changes, then recompiling the
client / server interface with AIL will probably be sufficient to port the server to the new RPC
interface. Another advantage is that it is in principle easier to write a specification of the
interface in AIL than to write the code for every interface stub.
Before writing servers and clients it is important to understand the system of F-boxes
described in many of the papers about Amoeba. These have been implemented in software.
The idea of the F-box is that it prevents someone starting a server which intercepts RPCs
intended for some other (important) server by deliberately listening to the same port.
The F-box mechanism works as follows. When a getreq call is done it listens to the get-port
for the server. This is a port known only to the server. The server’s kernel passes this port
through the routine priv2pub(L) to encrypt the port. The result is known as the put-port and
the kernel accepts requests sent to the put-port . Priv2pub is a one-way function so it is
practically impossible to deduce the get-port given the put-port .
Before doing a getreq the server needs to publish the put-port through which it can be found
by clients. Therefore it also passes the get-port through priv2pub and publishes the result in
the directory server in a place accessible to its clients. In principle it is not possible to listen
for requests on the public port unless the get-port is known.
This actually provides very little security if it is possible for someone to boot modified
Amoeba kernels on your network. They can add a primitive for listening to put-ports or
modify a kernel binary to avoid the conversion of get-port to put-port when getreq is called.
The same deficiency occurs if the F-box is implemented in hardware and it is possible to
connect to the network without an intervening F-box.
10 Amoeba 5.3
3.1. Servers
Most servers have the same basic structure, although exceptions do exist. Each has a main
program which initializes any global variables, publishes the port of the server so that clients
can use it and starts one or more instances of the server loop. The server loop is typically an
infinite loop with a getreq call at the top, a switch on the command to be executed and a
putrep call at the bottom. If the server is multithreaded then resource locking may also take
place within the infinite loop as well. It is a good idea to be able to see the matching pairs of
lock/unlock and getreq and putrep. Unmatched pairs can be difficult to debug so it is better
to keep them in the same function wherever possible.
If a server is expected to have more than one client then it is important to make it
multithreaded. This allows the server to take advantage of any possibility for parallelism in
handling requests. As soon as one thread gets a request, another will be ready to run with the
next RPC that arrives. It also means that there is a high probability that the server is listening
to its port and so locates of the server will not fail. Each thread in the server that handles
clients will normally run the same code. Of course, there may well be several other threads
inaccessible to clients performing different tasks within the server.
The best way to understand the structure of a server is by looking at one. Below is an
example of a server written in C. The example is of a trivial server which when sent two
long integers reverses their order in the message and sends them back to the client. This is a
ridiculous thing for a server to do since it is trivially done in the application itself but it does
demonstrate the important aspects of writing servers. Note that some unimportant details
have been left out to avoid obscuring the structure.
We begin with the include file this server.h which is particular to this server. It defines the
request buffer size REQ BUFSZ, the command codes for the commands that the server
accepts, the relevant rights bits for the server and the number of threads that the server should
run. They are in an include file since some of this information is also needed by the client or
for tuning.
Note that it is not acceptable to use just any command codes. You must begin at the value
UNREGISTERED FIRST COM defined in the file cmdreg.h as explained in the chapter
Paradigms and Implementations .
/* the include file this server.h */
#define NTHREADS 5
#define STACKSZ 0x4000
Before we look at how the server loop works it is worth knowing how to invoke multiple
Amoeba 5.3 11
threads all running the server loop. This is done using the thread interface routines (see
thread(L)). The use of priv2pub is also demonstrated.
#include "amoeba.h"
#include "stderr.h"
#include "module/rnd.h"
#include "module/name.h"
#include "this server.h"
main()
{
capability put cap;
errstat err;
int i;
server loop();
/*NOTREACHED*/
}
Note that if main exits then the process will die. Therefore it is important that main either
goes into an infinite loop (in this case by also executing the server loop) or goes to sleep (for
example, on a mutex).
In the server loop routine it is assumed that the initialization of global variables has taken
place in the main program which started it. In particular the port listened to by the server
12 Amoeba 5.3
should have been initialized and published under the name defined by SERVER CAP in the
include file this server.h . The directory where this capability is to be found must be writable
by the person starting the server. Otherwise the new capability cannot be registered.
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
#include "this server.h"
/*ARGSUSED*/
void
server loop(param, size)
char * param;
int size;
{
header hdr;
char reqbuf[REQ BUFSZ], repbuf[REQ BUFSZ];
bufsize n;
for (;;) {
hdr.h port = get cap.cap port;
n = getreq(&hdr, reqbuf, REQ BUFSZ);
if (ERR STATUS(n)) {
printf("getreq failed: %s\n", err why((errstat) n));
exit(1);
}
switch (hdr.h command) {
case DO SWAP:
hdr.h status = do swap(&hdr, reqbuf, n, repbuf);
break;
case DO NOTHING:
hdr.h status = do nothing(&hdr, reqbuf, n, repbuf);
break;
default:
hdr.h status = STD COMBAD;
hdr.h size = 0;
break;
}
putrep(&hdr, repbuf, hdr.h size);
}
}
The routine err why (see error(L)) produces a human readable error message for standard
error codes. For unknown error codes it merely returns the string
Amoeba 5.3 13
amoeba error nn
where nn is the error code returned. Note that getreq takes a get-port as parameter in the hdr
variable. However when it returns the hdr contains the put-port and other fields sent by the
client so it is important to reinitialize hdr before each call to getreq .
The routine do swap will check the capability in the header, take the n bytes of data in the
request buffer and perform the command specified by DO SWAP. It fills the reply buffer to
be returned to the client and returns the status of the command. It returns the status for
clarity. If every entry in the switch sets the return status of the command it is easy to check
that all pathways return a status. Other arrangements are also possible.
Now we shall consider the structure of the routine do swap. The structure of the data that it
expects in the request buffer determines what data the client must send to it. Since the server
should also provide the client programs with stub routines to communicate with the server,
the routine do swap will largely determine the client stubs. However, we first need a way of
validating capabilities. Below is an example routine. Note that it is important to distinguish
between a bad capability and a capability with insufficient rights when generating an error
code.
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
#include "module/prv.h"
extern port check field;
errstat
check cap(priv, required rights)
private * priv;
rights bits required rights;
{
rights bits rights;
if (prv number(priv) != 0 ||
prv decode(priv, &rights, &check field) != 0)
return STD CAPBAD;
if ((rights & required rights) != required rights)
return STD DENIED;
return STD OK;
}
In this example the server has only one capability of interest, namely the capability for the
server itself. There are no object capabilities. Note well that the capability is checked
against the original check field and not against the get cap. If you have more than one
object then the original check field for each object should be stored with the per-object
information and not the get or put capability. The check field can be used to generate the
capability, but the converse may not be true. The prv(L) routines should always be used the
to check capabilities to protect programs from future changes to data structures.
14 Amoeba 5.3
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
#include "module/buffers.h"
#include "this server.h"
errstat
do swap(hdr, req, reqsz, rep)
header * hdr;
char * req;
bufsize reqsz;
char * rep;
{
char * p;
char * q;
long val1, val2;
errstat err;
q = req;
p = rep;
The routine do swap uses the buf get and buf put routines (see buffer (L)) to receive and
send data. These ensure that no byte-order problems occur between client and server. The
stub routines should also use the corresponding routines. The buf get and buf put routines
are very pleasant to use since it is not necessary to check for errors until the last operation is
completed. They check for buffer overflow and thus prevent overrunning array bounds.
Amoeba 5.3 15
They return the next free position in the buffer if they succeed and the null pointer if they
fail. If given a null pointer as a buffer argument then they immediately return a null pointer.
Thus if any of the calls returns a null pointer then all subsequent calls will also do so and the
error test need only be done once at the end of a series of get or put calls.
In this example the server expects two longs in the request buffer. It swaps them and inserts
the result into the reply buffer using buf put long and returns the total size of the reply
buffer to the server loop which sends the reply.
16 Amoeba 5.3
3.2. Clients
It is now time to consider what is needed on the client side. It is possible to write a client
program which knows all about the data formats that the server expects. However it is
almost certainly simpler to provide the client with single routine to call which hides the
details of marshaling the data and sending it to the server. One reason for doing this is that
the implementation of the RPC is then hidden from the client program and any of the planned
changes to that interface need only be modified in a few stub routines and the programs
should then continue to function after recompilation. If AIL is used then recompilation of the
sources should be sufficient.
A stub routine for DO SWAP might look like the following.
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
#include "module/buffers.h"
#include "this server.h"
errstat
swap longs(svr, val1, val2)
capability * svr;
long * val1;
long * val2;
{
header hdr;
bufsize t;
char buf[REQ BUFSZ];
char * p;
In general it is important that stub routines return an error status. This makes it much simpler
Amoeba 5.3 17
for the client to deduce the cause of errors. Returning a null pointer or something similar on
failure complicates things and a single errno-like variable is complicated and cumbersome in
a multithreaded process.
Note once again the use of the buf put and buf get routines for marshaling data. There are
routines for various data types. It is important that the server and the stub marshal the data in
the same way or the results may be unfortunate.
In this example we used the same buffer to send data as to receive it. Similarly we used the
same header struct to send and receive information. This is perfectly acceptable practice but
the following should be noted. The fields h command and h status occupy the same
position in the header struct. (The field name h status is actually a #define.) Therefore if a
stub has a loop of RPCs rather than a single RPC then the h command field must be
reinitialized each time around the loop.
There are some fields in the header struct which are intended for small amounts of out of
band data. These could have been used to send and receive small pieces of data. In this
example we have two longs and they do not both fit into the header. All byte swapping of
header fields is done automatically by the kernel and if only one or two small integers need to
be sent then it is better to use the header and give the parameter NILBUF and buffer size zero
(0) since this will lead to greater efficiency in the marshaling of data.
The macros ERR STATUS and ERR CONVERT are also important. In the current version of
Amoeba the error status returned by trans or in hdr.h status is an unsigned 16-bit integer. It
is intended that this be changed to a signed 32-bit integer in a future release. However, error
codes are signed. To avoid massive changes later, stubs return a signed 32-bit integer and the
grizzly details of the current implementation are thus hidden from the clients and servers.
However it is necessary to make conversions that deal correctly with sign extension.
Therefore the macro ERR STATUS is provided to determine whether what was returned is
indeed an error and ERR CONVERT is provided to correctly convert the unsigned 16-bit
integer to a signed 32-bit quantity.
Now let us consider what the main program of the client looks like. One of the things it
needs to do is to get the capability for the server it needs to talk to. In general this is not done
by the stub routine because there may be more than one instance of a particular server and the
main program should be able to choose the server. It gets the server’s capability by doing a
name lookup of the name that the server should have used to publish its capability. In this
case it is the name defined by SERVER CAP in this server.h .
Having converted the command line arguments to integers, the next step is to call the stub
routine. This is a normal procedure call, but the stub then takes the server’s put capability
and the two integers and puts them into a message which it then sends out. The message it
gets back has the two numbers swapped around and it sets them into the original variables,
but now in the reverse order.
Thus the client might be implemented as follows:
18 Amoeba 5.3
#include "amoeba.h"
#include "stderr.h"
#include "stdlib.h"
#include "this server.h"
#include "module/name.h"
main(argc, argv)
int argc;
char * argv[];
{
errstat err;
capability svr;
long num1, num2;
if (argc != 3) {
printf("Usage: %s num1 num2\n", argv[0]);
exit(1);
}
Amoeba 5.3 19
3.3. Using AIL
Although it is possible to write the server loop and client stubs by hand it is also possible to
generate the stubs and server loop using the Amoeba Interface Language (AIL). All the
details of marshaling and unmarshaling data are handled by AIL. The precise details of how
to use AIL are described in ail(U) and the chapter Programming Tools in this volume of the
manual.
An important thing to watch out for is that AIL-generated servers or clients do not
necessarily cooperate with hand-written servers or clients. The reason for this is that AIL
may decide to marshal some of the parameters of an operation into the header. Another
optimization it currently performs is the marshaling of parameters to and from request and
reply buffers. Rather than marshaling the parameters in a byte-order independent way (using
the buf put routines, described earlier) it sends them over in the original byte-order.
Additionally, one of the header fields is initialized to allow the server to see whether it is
necessary to byte-swap parameters contained in the buffer.
In the next two sections we will reimplement the server described in the preceding sections.
Only this time we will show how much of the labour required can be transferred to AIL.
#include "cmdreg.h"
#include "this server.h"
};
It defines the class swaplong which consists of commands in the range from DO SWAP up to
DO NOTHING. The client stub for the command DO SWAP has three parameters: the ‘‘*’’,
representing the capability for the object on which the operation is performed, and two long
parameters that are passed by reference (the in out clauses are responsible for that). Note
that although the server in this case is not really object-based, the ‘‘*’’ is still required in
20 Amoeba 5.3
order to address the server.
The class presented here is very simple; in general it may also define class-specific types and
constants. Moreover, a class can inherit procedures, types and constants from other classes,
thus making it very easy to write a server supporting a subset of the standard server
commands (see std(L)). In fact all servers should do this. This is outside the scope of this
introduction however. For details see the AIL manuals.
To make use of the AIL class definition, the command ail(U) must be told what to do with it.
To generate the server main-loop which was hand-coded earlier, the following file should be
given as argument to ail:
/* file swapsvr.gen */
#include "swap.cls"
generate swaplong {
client interface;
server; /* Generate server main loop */
};
The C source for the server main-loop will be produced in the file ml swaplong.c. This file
will include a header file swaplong.h which is generated as well.
The main-loop generated is called ml swaplong, and it has a single parameter supplying the
get-port the server should be listening to. The code for the function server loop suddenly
becomes very simple:
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
#include "this server.h"
errstat ml swaplong();
capability get cap;
void
server loop()
{
errstat err;
As soon as a request has been accepted by ml swaplong it will unmarshal the parameters of
the command and call a user-supplied function taking care of the actual implementation of
the command. After that, it will marshal the result values (as specified by the client
interface), return the reply to the client, and wait for a new request.
The implementation functions for our swap server are called impl do swap and
impl do nothing. The code for impl do swap now becomes:
Amoeba 5.3 21
errstat
impl do swap(hdr, first, second)
header *hdr;
long *first, *second;
{
errstat check cap();
long temp;
errstat err;
temp = *second;
*second = *first;
*first = temp;
Observe that, compared with function do swap in the hand-coded example, the code is now
almost trivial, because the programmer is not bothered with the marshaling of input and
output data.
Combined with a completely analogous definition of impl do nothing and the functions
main and check cap taken from the original implementation, our AIL-based server is now
complete.
generate swaplong {
client interface;
client stubs(do swap);
command(do swap);
};
The clause client interface causes a header file swaplong.h containing C prototypes for the
client stubs to be generated. The client stub for the operation DO SWAP is requested by the
client stubs clause. It will be produced in the file do swap.c. The rest of the program,
which takes care of parsing the arguments, calling the stub and printing the results is
requested by the command clause. It will be produced in the file cmd do swap.c.
22 Amoeba 5.3
It must be noted that the command produced is a direct reflection of the do swap operation
as defined in the class definition. More specifically, it requires the user to specify the name
of the swap server capability as the first argument. The resulting program has the following
interface:
$ swap /home/this server/default 0 1
first=1
second=0
The other possibility is to use AIL only to generate the client stub do swap. The AIL
specification becomes:
#include "swap.cls"
generate swaplong {
client interface;
client stubs(do swap);
};
The main program is then the same as in the hand-written case, except that the client stub is
called do swap rather than swap longs.
Amoeba 5.3 23
4 Amoeba Under Unix
The FLIP network protocol can be installed in many popular versions of UNIX so that
processes running under UNIX can communicate using the Amoeba RPC. Many programs
that run under Amoeba will also allow access to Amoeba from UNIX. However not all
programs that run under native Amoeba will work under UNIX since UNIX does not support
multithreaded processes in a the same way as Amoeba. Many threads packages are available
that use non-preemptive scheduling between threads so compatibility with Amoeba should be
possible but later releases may present problems. There is a special library for linking
programs that use the Amoeba RPC but which are to run under UNIX. It is called
libamunix.a . To see how to use it, examine the Amakefiles that are distributed with Amoeba
for generating programs to run under UNIX.
Because of limitations in the UNIX networking code, the Amoeba protocols inside a UNIX
kernel are usually slower than under native Amoeba. This can lead to the fast client − slow
server problem. In this case a server running on a slow system receives a request, executes it
and sends a reply. However, before it can issue another getreq the client is trying to send
another request. Since the server is temporarily unavailable, the client issues one or more
locate broadcasts which can grow into broadcast storms. Under Amoeba this can be avoided
by having multiple threads doing a getreq on the same port so that a locate always succeeds.
The problem of busy servers will be solved in a subsequent release.
Two commands which are very useful under UNIX are tob(U) and fromb (U) which allow
files to be sent between UNIX and the Bullet file server.
Amoeba has a special server for converting between its network protocol and TCP/IP (see
ipsvr(A)). With the aid of this and the program ttn(U) (an implementation of telnet) it is
possible to do remote login to UNIX hosts. Electronic mail can also be transferred between
Amoeba and public TCP/IP networks using the TCP/IP server.
24 Amoeba 5.3
5 Programming Languages
This section describes the various programming languages available under Amoeba.
The Amsterdam Compiler Kit (ACK) provides much of the language support for Amoeba.
Several languages are currently provided: STD C, Pascal, BASIC, FORTRAN 77 and
Modula-2. The target architectures currently supported by the compiler under Amoeba are
the MC680x0 family and the Intel 80386.
Details of how to invoke these compilers are provided in the User Guide. See ack(U), cc(U),
f77(U), m2(U) and pascal(U) for further details. In addition an archiver, name list and
sizing program are provided. See aal(U), anm(U) and size (U) for details.
The internal details of ACK, including the link editor and information about descriptions files
for front and back-ends is rather esoteric and irrelevant to most programmers. However for
completeness there is a description of the structure of the compiler kit and the manual pages
for the linker and description files. Also included is a specification of the conformance of the
STD C, Pascal and Modula 2 compilers.
In addition to ACK the GNU C Compiler is available under Amoeba for all supported
architectures. It is freely available but does not form part of the distribution. For details of
its use see the separate GNU documentation for the compiler. The command f2c(U) can be
used as a preprocessor for the GNU C compiler to compile Fortran 77.
Amoeba 5.3 25
5.1. The Amsterdam Compiler Kit (ACK)
Introduction
The Amsterdam Compiler Kit (ACK) is an integrated collection of programs designed to
simplify the task of producing portable (cross) compilers and interpreters. For each language
to be compiled, a program (called the front end) must be written to translate the source
program into a common intermediate code called EM (short for Encoding Machine). The EM
code can be optimized and then either directly interpreted or translated into the assembly
language of the desired target machine using a program called the back end.
The ideal situation would be an integrated system containing a family of (cross) compilers,
each compiler accepting a standard source language and producing code for a wide variety of
target machines. Furthermore, the compilers should be compatible, so programs written in
one language can call procedures written in another language. This has largely been
achieved.
ACK consists of a number of parts that can be combined to form compilers (and interpreters)
with various properties. It is based on the idea of a Universal Computer Oriented Language
(UNCOL) that was first suggested in 1960, but which never really caught on then. The
problem which UNCOL attempts to solve is how to make a compiler for each of N languages
on M different machines without having to write N x M programs.
The UNCOL approach is to write N ‘‘front ends,’’ each of which translates one source
language to a common intermediate language, UNCOL , and M "back ends," each of which
translates programs in UNCOL to a specific machine language.
Under these conditions, only N + M programs must be written to provide all N languages on
all M machines, instead of N x M programs.
Previous attempts to develop a suitable intermediate language have not become popular
because they were over ambitious and attempted to support all languages and target
architectures. ACK’s approach is more modest: it caters only to algebraic languages and
machines whose memory consists of 8-bit bytes, each with its own address. Typical
languages that could be handled include Ada, ALGOL 60, ALGOL 68, BASIC, C,
FORTRAN, Modula, Pascal, PL/I, PL/M, and RATFOR, whereas COBOL, LISP, and
SNOBOL would be less efficient. Examples of machines that could be included are the Intel
8080 and 80x86, Motorola 6800, 6809, and 680x0, Zilog Z80 and Z8000, DEC PDP-11 and
VAX, and IBM 370 but not the Burroughs 6700, CDC Cyber, or Univac 1108 (because they
are not byte-oriented). With these restrictions, the old UNCOL idea has been used as the
basis of a practical compiler-building system.
26 Amoeba 5.3
1. The preprocessor.
2. The front ends.
3. The peephole optimizer.
4. The global optimizer.
5. The back end.
6. The target machine optimizer.
7. The universal assembler/linker.
8. The utility package.
A fully optimizing compiler, depicted in figure 5.1, has seven cascaded phases.
Conceptually, each component reads an input file and writes a output file to be used as input
to the next component. In practice, some components may use temporary files to allow
multiple passes over the input or internal intermediate files.
Front
End 1
Universal
Pre- Peephole Global Back Target
Assembler-
processor Optimizer Optimizer End Optimizer
Linker
Front
source Object
End N
program machine machine Program
independent dependent
table tables
Amoeba 5.3 27
important special cases efficiently (for example, incrementing a variable by 1). The strategy
is to relieve the front ends of the burden of hunting for special cases because there are many
front ends and only one peephole optimizer. By handling the special cases in the peephole
optimizer, the front ends become simpler, easier to write and easier to maintain.
Following the peephole optimizer is a global optimizer, which unlike the peephole optimizer,
examines the program as a whole. It builds a data flow graph to make possible a variety of
global optimizations, among them, moving invariant code out of loops, avoiding redundant
computations, live/dead analysis and eliminating tail recursion. Note that the output of the
global optimizer is still EM code.
Next comes the back end, which differs from the front ends in a fundamental way. Each
front end is a separate program, whereas the back end is a single program that is driven by a
machine dependent driving table. The driving table for a specific machine tells how the EM
code is mapped onto the machine’s assembly language. Although a simple driving table
might just macro expand each EM instruction into a sequence of target machine instructions, a
much more sophisticated translation strategy is normally used, as described later. For speed,
the back end does not actually read in the driving table at run-time. Instead, the tables are
compiled along with the back end in advance, resulting in one binary program per machine.
The output of the back end is a program in the assembly language of some particular
machine. The next component in the pipeline reads this program and performs peephole
optimization on it. The optimizations performed here involve idiosyncrasies of the target
machine that cannot be performed in the machine-independent EM-to-EM peephole
optimizer. Typically these optimizations take advantage of special instructions or special
addressing modes.
The optimized target machine assembly code then goes into the final component in the
pipeline, the universal assembler/linker. This program assembles the input to object format,
extracting routines from libraries and including them as needed.
The final component of the tool kit is the utility package, which contains various test
programs, interpreters for EM code, EM libraries, conversion programs, and other aids for the
implementer and user.
28 Amoeba 5.3
5.1.1. ACK Description File Reference Manual
Introduction
The program ack(U) internally maintains a table of possible transformations and a table of
string variables. The transformation table contains one entry for each possible
transformation of a file. Which transformations are used depends on the suffix of the source
file. Each transformation table entry tells which input suffices are allowed and what
suffix/name the output file has. When the output file does not already satisfy the request of
the user (indicated with the flag −c.suffix), the table is scanned starting with the next
transformation in the table for another transformation that has as input suffix the output
suffix of the previous transformation. A few special transformations are recognized, among
them is the combiner, which is a program combining several files into one. When no stop
suffix was specified (flag −c.suffix) ack stops after executing the combiner with as
arguments the − possibly transformed − input files and libraries. Ack will only perform the
transformations in the order in which they are presented in the table.
The string variables are used while creating the argument list and program call name for a
particular transformation.
Amoeba 5.3 29
Using The Description File
Before starting on a narrative of the description file, the introduction of a few terms is
necessary. All these terms are used to describe the scanning of zero terminated strings,
thereby producing another string or sequence of strings.
Backslashing
All characters preceded by \ are modified to prevent recognition at further scanning.
This modification is undone before a string is passed to the outside world as argument
or message. When reading the description files the sequences \\, \# and \<newline>
have a special meaning. \\ translates to a single \, \# translates to a single # that is not
recognized as the start of comment, but can be used in recognition and finally,
\<newline> translates to nothing at all, thereby allowing continuation lines.
Variable replacement
The scan recognizes the sequences {{, {NAME} and {NAME?text} where NAME can
be any combination if characters excluding ? and } and text may be anything excluding
}. ( \} is allowed of course. ) The first sequence produces an unescaped single {. The
second produces the contents of the NAME, definitions are done by ack and in
description files. When the NAME is not defined an error message is produced on the
diagnostic output. The last sequence produces the contents of NAME if it is defined
and text otherwise.
Expression replacement
Syntax: (suffix sequence:suffix sequence=text)
Example: (.c.p.e:.e=tail em)
If the two suffix sequences have a common member − .e in this case − the text is
produced. When no common member is present the empty string is produced. Thus
the example given is a constant expression. Normally, one of the suffix sequences is
produced by variable replacement. Ack sets three variables while performing the
diverse transformations: HEAD, TAIL and RTS. All three variables depend on the
properties rts and need from the transformations used. Whenever a transformation is
used for the first time, the text following the need is appended to both the HEAD and
TAIL variable. The value of the variable RTS is determined by the first transformation
used with a rts property.
Two run-time flags have effect on the value of one or more of these variables. The
flag −.suffix has the same effect on these three variables as if a file with that suffix
was included in the argument list and had to be translated. The flag −r.suffix only has
that effect on the TAIL variable. The program call names acc and cc have the effect
of an automatic −.c flag. Apc and pc have the effect of an automatic −.p flag.
Line splitting
The string is transformed into a sequence of strings by replacing the blank space by
string separators (nulls).
IO replacement
The > in the string is replaced by the output file name. The < in the string is replaced
by the input file name. When multiple input files are present the string is duplicated
for each input file name.
Each description is a sequence of variable definitions followed by a sequence of
transformation definitions. Variable definitions use a line each, transformations definitions
30 Amoeba 5.3
consist of a sequence of lines. Empty lines are discarded, as are lines with nothing but
comment. Comment is started by a # character, and continues to the end of the line. Three
special two-characters sequences exist: \#, \\ and \<newline>. Their effect is described under
’backslashing’ above. Each − nonempty − line starts with a keyword, possibly preceded by
blank space. The keyword can be followed by a further specification. The two are separated
by blank space.
Variable definitions use the keyword var and look like this:
var NAME=text
The name can be any identifier, the text may contain any character. Blank space before the
equal sign is not part of the NAME. Blank space after the equal is considered as part of the
text. The text is scanned for variable replacement before it is associated with the variable
name.
The start of a transformation definition is indicated by the keyword name . The last line of
such a definition contains the keyword end. The lines in between associate properties to a
transformation and may be presented in any order. The identifier after the name keyword
determines the name of the transformation. This name is used for debugging and by the −R
flag. The keywords are used to specify which input suffices are recognized by that
transformation, the program to run, the arguments to be handed to that program and the name
or suffix of the resulting output file. Two keywords are used to indicate which run-time
start-offs and libraries are needed. The possible keywords are:
from
followed by a sequence of suffices. Each file with one of these suffices is allowed as
input file. Preprocessor transformations do not need the from keyword. All other
transformations do.
to
followed by the suffix of the output file name or in the case of a linker the output file
name.
program
followed by name of the load file of the program, a path name most likely starts with
either a / or {EM}. This keyword must be present, the remainder of the line is subject to
backslashing and variable replacement.
mapflag
The mapflags are used to grab flags given to ack and pass them on to a specific
transformation. This feature uses a few simple pattern matching and replacement
facilities. Multiple occurrences of this keyword are allowed. This text following the
keyword is subjected to backslashing. The keyword is followed by a match expression
and a variable assignment separated by blank space. As soon as both description files
are read, ack looks at all transformations in these files to find a match for the flags
given to ack. The flags −m, −o, −O, −r, −v, −g, −−c, −t, −k, −R and −−. are specific
to ack and not handed down to any transformation. The matching is performed in the
order in which the entries appear in the definition. The scanning stops after first match
is found. When a match is found, the variable assignment is executed. A * in the match
expression matches any sequence of characters, a * in the right hand part of the
Amoeba 5.3 31
assignment is replaced by the characters matched by the * in the expression. The right
hand part is also subject to variable replacement. The variable will probably be used in
the program arguments. The −l flags are special, the order in which they are presented
to ack must be preserved. The identifier LNAME is used in conjunction with the
scanning of −l flags. The value assigned to LNAME is used to replace the flag. The
example further on shows the use of all this.
args
The keyword is followed by the program call arguments. It is subject to backslashing,
variable replacement, expression replacement, line splitting and IO replacement. The
variables assigned to by mapflags will probably be used here. The flags not recognized
by ack or any of the transformations are passed to the linker and inserted before all
other arguments.
stdin
This keyword indicates that the transformation reads from standard input.
stdout
This keyword indicates that the transformation writes on standard output.
optimizer
The presence of this keyword indicates that this transformation is an optimizer. It can
be followed by a number, indicating the "level" of the optimizer (see description of the
−O option in the ack(U) manual page).
priority
This optional keyword is followed by a number. Positive priority means that the
transformation is likely to be used, negative priority means that the transformation is
unlikely to be used. Priorities can also be set with a ack(U) command-line option.
Priorities come in handy when there are several implementations of a certain
transformation. They can then be used to select a default one.
linker
This keyword indicates that this transformation is the linker.
combiner
This keyword indicates that this transformation is a combiner. A combiner is a program
combining several files into one, but is not a linker. An example of a combiner is the
global optimizer.
prep
This optional keyword is followed an option indicating its relation to the preprocessor.
The possible options are:
always the input files must be preprocessed
cond the input files must be preprocessed when starting with #
is this transformation is the preprocessor
rts
This optional keyword indicates that the rest of the line must be used to set the variable
RTS, if it was not already set. Thus the variable RTS is set by the first transformation
executed which such a property or as a result from ack’s program call name (acc, cc,
apc or pc) or by the −.suffix flag.
32 Amoeba 5.3
need
This optional keyword indicates that the rest of the line must be concatenated to the
NEEDS variable. This is done once for every transformation used or indicated by one
of the program call names mentioned above or indicated by the −.suffix flag.
Example
Description for front-end
Amoeba 5.3 33
name cpp # the C-preprocessor
# no from, it’s governed by the P property
to .i # result files have suffix i
program {EM}/lib/cpp # path name of loadfile
mapflag −I* CPP F={CPP F?} −I* # grab −I.. −U.. and
mapflag −U* CPP F={CPP F?} −U* # −D.. to use as arguments
mapflag −D* CPP F={CPP F?} −D* # in the variable CPP F
args {CPP F?} {INCLUDES?} −D{NAME} −DEM WSIZE={w} −DEM PSIZE={p} \
−DEM SSIZE={s} −DEM LSIZE={l} −DEM FSIZE={f} −DEM DSIZE={d} <
# The arguments are: first the −[IUD]...
# then the include dir’s for this machine
# then the NAME and size values finally
# followed by the input file name
stdout # Output on stdout
prep is # Is preprocessor
end
name cem # the C-compiler proper
from .c # used for files with suffix .c
to .k # produces compact code files
program {EM}/lib/em cem # path name of loadfile
mapflag −p CEM F={CEM F?} −Xp # pass −p as −Xp to cem
mapflag −L CEM F={CEM F?} −l # pass −L as −l to cem
args −Vw{w}i{w}p{p}f{f}s{s}l{l}d{d} {CEM F?}
# the arguments are the object sizes in
# the −V... flag and possibly −l and −Xp
stdin # input from stdin
stdout # output on stdout
prep always # use cpp
rts .c # use the C run-time system
need .c # use the C libraries
end
name decode # make human readable files from compact code
from .k.m # accept files with suffix .k or .m
to .e # produce .e files
program {EM}/lib/em decode # path name of loadfile
args < # the input file name is the only argument
stdout # the output comes on stdout
end
34 Amoeba 5.3
Example of a back end, in this case the EM assembler/loader.
Amoeba 5.3 35
5.1.2. The Link Editor
Synopsis
led [options] file ...
Description
Led is a link editor (linker) for object modules, created by one of the ACK assemblers. It
combines several object programs into one, resolves external references, and searches
archives. Usually, led is not invoked directly by the user, but via the generic compiler driver
ack(U). In the simplest case several object files are given, and led combines them,
producing an object module which can be either fed into a machine specific conversion
program or become the input for a further led run. (In the latter case, if there are no
unresolved references, the −r option must be given to preserve the relocation information.)
The resulting object file of led is named a.out.
The argument routines are concatenated in the order specified. The entry point of the output
is the beginning of the first routine.
If an argument is an archive, its table of contents is searched for names which are undefined
at the point at which the argument is encountered in the argument list. This procedure is
repeated as long as unresolved references are satisfied.
Options
Led understands several options. The flags −c, −r, −s, and −u should appear before the file
names.
−add:nnnn
The alignment of section dd, where dd is a decimal number, is set to nnnn. If nnnn
starts with ‘0x’, it is hexadecimal, else if its starts with ‘0b’, it is binary, else if it
starts with ‘0’, it is octal, else it is decimal.
−bdd:nnnn
The base address in the machine of section dd, is set to nnnn. The previous
remarks about dd and nnnn apply.
−o The name argument after −o is used as the name of the led output file, instead of
a.out.
−r Generate relocation information in the output file so that it can be the subject of
another led run. This flag suppresses the ‘Undefined:’ diagnostic.
−c Indicates that relocation information must be produced, but commons must be
resolved. This may be useful for machines that need a last relocation step at load
time. This flag disables the −r flag.
−s ‘Strip’ the output. That is, remove the name table and relocation information to
save space (but impair the usefulness of the debuggers).
−u Take the following argument as a symbol and enter it as undefined in the name
table. This is useful for loading wholly from a library, since initially the name
table is empty and an unresolved reference is needed to force the loading of the
first routine.
36 Amoeba 5.3
−v For each member of a library that is linked, give a message on standard error telling
why led chose to link it (which unresolved reference it resolves). This option is
useful if you have ‘multiply defined’ problems.
Files
/profile/module/ack/lib/back/any/em led: led binary
a.out: output file
See Also
ack(U).
Amoeba 5.3 37
5.1.3. ACK STD C Compiler
This document specifies the implementation-defined behavior of the STD C front end of the
Amsterdam Compiler Kit as required by ANS X3.159-1989. Although the C compiler
available on Amoeba is fully derived from the Standard Conforming ACK STD C Compiler,
some parts of the Amoeba libraries still require some work in order to reach full Standard C
conformance. Details of the Amoeba library conformance are in ansi C(L). A companion
document, posix(L), provides information about functions not defined by the C Standard but
by the POSIX standard, as well as additional information about functions defined (partially)
by both standards.
As the Amoeba Standard C library is also used by cross compilers for Amoeba that are not
necessarily STD C compatible, the section specifying implementation defined behavior of the
Standard C library (marked ANS A.6.3.14) deserves attention in any case.
38 Amoeba 5.3
ANS A.6.3.5:
The compiler assumes that it works on, and compiles for, a 2’s-complement binary-
number system. Shorts will use 2 bytes and longs will use 4 bytes. The size of integers
is machine dependent.
Converting an integer to a shorter signed integer is implemented by ignoring the high-
order byte(s) of the former. Converting a unsigned integer to a signed integer of the
same type is only done in administration. This means that the bit-pattern remains
unchanged.
The result of bitwise operations on signed integers are what can be expected on a 2-
complement machine.
If either operand is negative, whether the result of the / operator is the largest integer
less than or equal to the algebraic quotient or the smallest integer greater than or equal
to the algebraic quotient is machine dependent, as is the sign of the result of the %
operator.
The right-shift of a negative value is negative.
ANS A.6.3.6:
The representation of floating-point values is machine-dependent. When native
floating-point is not present an IEEE-emulation is used. The compiler uses high-
precision floating-point for constant folding.
Truncation is always to the nearest floating-point number that can be represented.
ANS A.6.3.7:
The type returned by the sizeof-operator (also known as size t) is ’unsigned int’. This
is done for backward compatibility reasons.
Casting an integer to a pointer or vice versa has no effect in bit-pattern when the sizes
are equal. Otherwise the value will be truncated or zero-extended (depending on the
direction of the conversion and the relative sizes).
When a pointer is as large as an integer, the type of a ’ptrdiff t’ will be ’int’. Otherwise
the type will be ’long’.
ANS A.6.3.8:
Since the front end has only limited control over the registers, it can only make it more
likely that variables that are declared as registers also end up in registers. The only
things that can possibly be put into registers are : ‘int’, ‘long’, ‘float’, ‘double’, ‘long
double’ and pointers.
ANS A.6.3.9:
When a member of a union object is accessed using a member of a different type, the
resulting value will usually be garbage. The compiler makes no effort to catch these
errors.
The alignment of types is a compile-time option. The alignment of a structure-member
is the alignment of its type. Usually, the alignment is passed on to the compiler by the
‘ack’ program. When a user wants to do this manually, he/she should be prepared for
trouble.
A plain ‘int’ bit-field is taken as a ‘signed int’. This means that a field with a size 1 bit
Amoeba 5.3 39
can only store the values 0 and −1.
The order of allocation of bit-fields is a compile-time option. By default, high-order
bits are allocated first.
An enum has the same size as a plain ‘int’.
ANS A.6.3.10:
An access to a volatile declared variable is done by just mentioning the variable. For
example, the statement x; where x is declared volatile, constitutes an access.
ANS A.6.3.11:
There is no fixed limit on the number of declarators that may modify an arithmetic,
structure or union type, although specifying too many may cause the compiler to run out
of memory.
ANS A.6.3.12:
The maximum number of cases in a switch-statement is in the order of 1e9, although the
compiler may run out of memory somewhat earlier.
ANS A.6.3.13:
Since both the preprocessor and the compiler assume ASCII-characters, a single
character constant in a conditional-inclusion directive matches the same value in the
execution character set.
The preprocessor recognizes –I... command-line options. The directories thus specified
are searched first. After that, depending on the command that the preprocessor is called
with, machine and system-dependent directories are searched. After that,
/profile/module/ack/include/posix and /profile/module/ack/include are visited.
Quoted names are first looked for in the directory in which the file which does the
include resides.
The characters in a h- or q- character sequence are taken to be path names.
Neither the compiler nor the preprocessor know any pragmas.
Since the compiler runs on Amoeba, DATE and TIME will always be defined.
When the time-of-day server does not respond within few seconds, time and date will be
set to the local equivalent of Jan 1, 1970.
ANS A.6.3.14:
NULL is defined as ((void *) 0). This in order to flag dubious constructions like
int x = NULL;.
The diagnostic printed by ‘assert’ is as follows:
Assertion "<expr>" failed, file "<file>", line <line>
where <expr> is the argument to the assert macro, printed as string. (the <file> and
<line> should be clear)
40 Amoeba 5.3
The sets for character test macros.
name: set:
isalnum() 0-9A-Za-z
isalpha() A-Za-z
iscntrl() \000-\037\177
islower() a-z
isupper() A-Z
isprint() <space>- (== \040-\176)
As an addition, there is an isascii() macro, which tests whether a character is an ASCII
character. Characters in the range from \000 to \177 are ASCII characters.
The behavior of mathematical functions on domain error:
name: returns:
asin() 0.0
acos() 0.0
atan2() 0.0
fmod() 0.0
log() −HUGE VAL
log10() −HUGE VAL
pow() 0.0
sqrt() 0.0
Amoeba 5.3 41
once. Having a file open for reading multiple times is safe, however.
When a remove() is done on an open file, reading and writing behave just as can be
expected from a non-removed file. When the associated stream is closed, all written
data will be retained, as a result of the file semantics described above.
When the destination file exists prior to a call to rename() , it is removed first.
The %p conversion in fprintf() has the same effect as %#x or %#lx, depending on the
sizes of pointer and integer.
The %p conversion in fscanf() has the same effect as %x or %lx, depending on the sizes
of pointer and integer.
A ‘−’ character that is neither the first nor the last character in the scanlist for %[
conversion is taken to be a range indicator. When the first character has a higher
ASCII-value than the second, the ‘−’ will just be put into the scanlist.
The value of errno when fgetpos() or ftell() failed is that of lseek(). This means:
EBADF − when the stream is not valid
ESPIPE − when fildes is associated with a pipe or another non-file server
EINVAL − when an invalid seek mode is specified, or when the resulting file pointer
would be negative
EIO − when the bullet file server does not respond.
The messages generated by perror() depend on the value of errno. The mapping of
errors to strings is done by strerror() .
When the requested size is zero, malloc() , calloc() and realloc() return a null-pointer.
When abort() is called, output buffers will be flushed. Temporary files (made with the
tmpfile() function) will have disappeared when SIGABRT is not caught or ignored.
The exit() function returns the low-order eight bits of its argument to the environment.
The predefined environment names are controlled by the user. Setting environment
variables is done through the putenv() function. This function accepts a pointer to char
as its argument. Example: to set the environment variable TERM to a230 one writes
static char terminal[] = "TERM=a230"; putenv(terminal);
The argument to putenv() is stored in an internal table, strings allocated with malloc()
can not be freed until another call to putenv() (which sets the same environment
variable) is made. The argument to putenv() must be writable, which means that
officially, the argument cannot be a string constant. The function returns 1 if it fails, 0
otherwise.
The argument to system is passed as argument to /bin/sh -c.
The strings returned by strerror() depend on errno in the following way:
errno string
0 "Error 0"
EPERM "Operation not permitted"
ENOENT "No such file or directory"
ESRCH "No such process"
EINTR "Interrupted system call"
42 Amoeba 5.3
EIO "Input/output error"
ENXIO "No such device or address"
E2BIG "Arg list too long"
ENOEXEC "Exec format error"
EBADF "Bad file descriptor"
ECHILD "No child processes"
EAGAIN "Resource temporarily unavailable"
ENOMEM "Not enough core"
EACCES "Permission denied"
EFAULT "Bad address"
EBUSY "Resource busy"
EEXIST "File exists"
ENODEV "No such device"
ENOTDIR "Not a directory"
EISDIR "Is a directory"
EINVAL "Invalid argument"
ENFILE "Too many open files in system"
EMFILE "Too many open files"
ENOTTY "Inappropriate ioctl operation"
EFBIG "File too large"
ENOSPC "No space left on device"
ESPIPE "Invalid seek"
EROFS "Read-only file system"
EMLINK "Too many links"
EPIPE "Broken pipe"
EDOM "Domain error"
ERANGE "Result too large"
EDEADLK "Operation would block"
ENAMETOOLONG "File name too long"
ENOTEMPTY "Directory not empty"
ENOLCK "No locks available"
ENOSYS "Function not implemented"
Errors that lie within the range 0..ENOSYS, and are not presented in the table cause
strerror() to return ‘‘Error <num>’’, where <num> is the error number in decimal.
Everything else causes strerror() to return ‘‘unknown error’’
The local time zone is per default MET (GMT + 1:00:00). This can be changed through
the TZ environment variable.
The function clock() always returns -1 on Amoeba.
References
[1] ANS X3.159-1989 American National Standard for Information Systems -
Programming Language C
Amoeba 5.3 43
See Also
posix(L), ctime(L), bullet(A), soap(A).
44 Amoeba 5.3
5.1.4. ACK Pascal Compiler Compliance Statements
Introduction
This document refers to the (1982) BSI standard for Pascal [1]. Ack-Pascal complies with
the requirements of level 1 of BS 6192: 1982, with the exceptions as listed in this document.
The standard requires an accompanying document describing the implementation-defined
and implementation-dependent features, the reaction on errors and the extensions to standard
Pascal. These four items will be treated in the rest of this document, each in a separate
section. The other chapters describe the deviations from the standard and the list of options
recognized by the compiler.
The Ack-Pascal compiler produces code for an EM machine as defined in [2]. It is up to the
implementor of the EM machine to decide whether errors like integer overflow, undefined
operand and range bound error are recognized or not.
There does not (yet) exist a hardware EM machine. Therefore, EM programs must be
interpreted, or translated into instructions for a target machine. The Ack-Pascal compiler is
currently available for use with the VAX, Motorola MC680x0, Intel 80x86, PDP-11, and
Intel 8086 code-generators. For the 8086, and MC680x0, floating point emulation is used.
This is made available with the −fp option, which must be passed to ack(U).
Implementation-defined features
For each implementation-defined feature mentioned in the BSI standard we give the section
number, the quotation from that section and the definition. First we quote the definition of
implementation-defined:
Possibly differing between processors, but defined for any particular processor.
BS 6.1.7: Each string-character shall denote an implementation-defined value of the required
char-type.
All 7-bits ASCII characters except linefeed LF (10) are allowed.
BS 6.4.2.2: The values of type real shall be an implementation-defined subset of the real
numbers denoted as specified in 6.1.5 by signed real.
The format of reals is not defined in EM. Even the size of reals depends on the EM-
implementation. The compiler can be instructed, by the V option, to use a different size
for real values. The size of reals is preset by the calling program ack(U) to the proper
size.
BS 6.4.2.2: The type char shall be the enumeration of a set of implementation-defined
characters, some possibly without graphic representations.
The 7-bits ASCII character set is used, where LF (10) denotes the end-of-line marker on
text-files.
BS 6.4.2.2: The ordinal numbers of the character values shall be values of integer-type, that
are implementation-defined, and that are determined by mapping the character values on to
consecutive non-negative integer values starting at zero.
The normal ASCII ordering is used: ord(’0’)=48, ord(’A’)=65, ord(’a’)=97, etc.
Amoeba 5.3 45
BS 6.6.5.2: The post-assertions imply corresponding activities on the external entities, if any,
to which the file-variables are bound. These activities, and the point at which they are
actually performed, shall be implementation-defined.
The reading and writing of objects on files is buffered. This means that when a program
terminates abnormally, IO may be unfinished. Terminal IO is unbuffered. Files are
closed whenever they are rewritten or reset, or on program termination.
BS 6.7.2.2: The predefined constant maxint shall be of integer-type and shall denote an
implementation-defined value, that satisfies the following conditions:
(a) All integral values in the closed interval from −maxint to +maxint shall be values of the
integer-type.
(b) Any monadic operation performed on an integer value in this interval shall be correctly
performed according to the mathematical rules for integer arithmetic.
(c) Any dyadic integer operation on two integer values in this same interval shall be
correctly performed according to the mathematical rules for integer arithmetic, provided
that the result is also in this interval.
(d) Any relational operation on two integer values in this same interval shall be correctly
performed according to the mathematical rules for integer arithmetic.
The representation of integers in EM is a n*8-bit word using two’s complement
arithmetic. Where n is called wordsize. The range of available integers depends on the
EM implementation: For 2-byte machines, the integers range from −32767 to +32767.
For 4-byte machines, the integers range from −2147483647 to 2147483647. The
number −maxint−1 may be used to indicate ‘undefined’.
BS 6.7.2.2: The result of the real arithmetic operators and functions shall be approximations
to the corresponding mathematical results. The accuracy of this approximation shall be
implementation-defined.
Since EM does not specify floating point format, it is not possible to specify the
accuracy. When the floating point emulation is used, and the default size of reals is 8
bytes, the accuracy is 11 bits for the exponent, and 53 bits for the mantissa. This gives
an accuracy of about 16 digits, and exponents ranging from −309 to +307.
BS 6.9.3.1: The default TotalWidth values for integer, Boolean and real types shall be
implementation-defined.
The defaults are:
integer 6 for 2-byte machines, 11 for 4-byte machines
Boolean 5
real 14
BS 6.9.3.4.1: ExpDigits, the number of digits written in an exponent part of a real, shall be
implementation-defined.
ExpDigits is defined as 3. This is sufficient for all implementations currently available.
When the representation would need more than 3 digits, then the string ’***’ replaces
the exponent.
BS 6.9.3.4.1: The character written as part of the representation of a real to indicate the
beginning of the exponent part shall be implementation-defined, either ’E’ or ’e’.
46 Amoeba 5.3
The exponent part starts with ’e’.
BS 6.9.3.5: The case of the characters written as representation of the Boolean values shall
be implementation-defined.
The representations of true and false are ’true’ and ’false’.
BS 6.9.5: The effect caused by the standard procedure page on a text file shall be
implementation-defined.
The ASCII character form feed FF (12) is written.
BS 6.10: The binding of the variables denoted by the program-parameters to entities external
to the program shall be implementation-defined if the variable is of a file-type.
The program parameters must be files and all, except input and output, must be declared
as such in the program block.
The program parameters input and output, if specified, will correspond with the Amoeba
streams ’standard input’ and ’standard output’.
The other program parameters will be mapped to the argument strings provided by the caller
of this program. The argument strings are supposed to be path names of the files to be
opened or created. The order of the program parameters determines the mapping: the first
parameter is mapped onto the first argument string etc. Note that input and output are
ignored in this mapping.
The mapping is recalculated each time a program parameter is opened for reading or writing
by a call to the standard procedures reset or rewrite. This gives the programmer the
opportunity to manipulate the list of string arguments using the external procedures argc,
argv and argshift available in libpc(L).
BS 6.10: The effect of an explicit use of reset or rewrite on the standard textfiles input or
output shall be implementation-defined.
The procedures reset and rewrite are no-ops if applied to input or output.
Implementation-dependent Features
For each implementation-dependent feature mentioned in the BSI standard, we give the
section number, the quotation from that section and the way this feature is treated by the
Ack-Pascal system. First we quote the definition of ‘‘implementation-dependent’’:
Possibly differing between processors and not necessarily defined for any
particular processor.
BS 6.7.2.1: The order of evaluation of the operands of a dyadic operator shall be
implementation-dependent.
All operands are always evaluated, so the program part
if (p<>nil) and (pˆ.value<>0) then
is probably incorrect.
The left-hand operand of a dyadic operator is almost always evaluated before the right-hand
side. Some peculiar evaluations exist for the following cases:
1. The modulo operation is performed by a library routine to check for negative values of
Amoeba 5.3 47
the right operand.
2. The expression
set1 <= set2
where set1 and set2 are compatible set types is evaluated in the following steps:
− evaluate set2
− evaluate set1
− compute set2+set1
− test set2 and set2+set1 for equality
3 The expression
set1 >= set2
where set1 and set2 are compatible set types is evaluated in the following steps:
− evaluate set1
− evaluate set2
− compute set1+set2
− test set1 and set1+set2 for equality
BS 6.7.3: The order of evaluation, accessing and binding of the actual-parameters for
functions shall be implementation-dependent.
The order of evaluation is from right to left.
BS 6.8.2.2: The decision as to the order of accessing the variable and evaluating the
expression in an assignment-statement, shall be implementation-dependent.
The expression is evaluated first.
BS 6.8.2.3: The order of evaluation and binding of the actual-parameters for procedures shall
be implementation-dependent.
The same as for functions.
BS 6.9.5: The effect of inspecting a text file to which the page procedure was applied during
generation is implementation-dependent.
The formfeed character written by page is treated like a normal character, with ordinal
value 12.
BS 6.10: The binding of the variables denoted by the program-parameters to entities external
to the program shall be implementation-dependent unless the variable is of a file-type.
Only variables of a file-type are allowed as program parameters.
Error handling
There are three classes of errors to be distinguished. In the first class are the error messages
generated by the compiler. The second class consists of the occasional errors generated by
the other programs involved in the compilation process. Errors of the third class are the
errors as defined in the standard by:
An error is a violation by a program of the requirements of this standard
that a processor is permitted to leave undetected.
48 Amoeba 5.3
Compiler Errors
Errors are written on the standard error output. Each line has the form:
<file>, line <number>: <description>
Every time the compiler detects an error that does not have influence on the code produced
by the compiler or on the syntax decisions, a warning messages is given. If only warnings
are generated, compilation proceeds and probably results in a correctly compiled program.
Sometimes the compiler produces several errors for the same line. They are only shown up
to a maximum of 5 errors per line. Warning are also shown up to a maximum of 5 per line.
Extensive treatment of these errors is outside the scope of this manual.
Run-time Errors
Errors detected at run time cause an error message to be generated on the diagnostic output
stream (Amoeba file descriptor 2). The message consists of the name of the program
followed by a message describing the error, possibly followed by the source line number.
Unless the –L option is turned on, the compiler generates code to keep track of which source
line causes which EM instructions to be generated. It depends on the EM implementation
whether these LIN instructions are skipped or executed.
For each error mentioned in the standard we give the section number, the quotation from that
section and the way it is processed by the Pascal compiler or run-time system.
For detected errors the corresponding message and trap number are given. Trap numbers are
useful for exception-handling routines. Normally, each error causes the program to
terminate. By using exception-handling routines one can ignore errors or perform alternate
actions. Only some of the errors can be ignored by restarting the failing instruction. These
errors are marked as non-fatal, all others as fatal. A list of errors with trap number between 0
and 63 (EM errors) can be found in [2]. Errors with trap number between 64 and 127 (Pascal
errors) have the following meaning:
Amoeba 5.3 49
96: file xxx: not writable
97: file xxx: not readable
98: file xxx: end of file
99: file xxx: truncated
100: file xxx: reset error
101: file xxx: rewrite error
102: file xxx: close error
103: file xxx: read error
104: file xxx: write error
105: file xxx: digit expected
106: file xxx: non-ASCII char read
50 Amoeba 5.3
BS 6.5.3.3: It shall be an error if a component of a variant-part of a variant, where the
selector of the variant-part is not a field, is accessed unless the variant is active for the
entirety of each reference and access to each component of the variant.
This error is not detected.
BS 6.5.4: It shall be an error if the pointer-variable of an identified-variable either denotes a
nil-value or is undefined.
The EM definition does not specify the binary representation of pointer values, so it is
not possible to choose an otherwise illegal binary representation for the pointer value
NIL. Rather arbitrarily the compiler uses the integer value zero to represent NIL. For
all current implementations this does not cause problems.
The size of pointers depends on the implementation and is preset in the compiler by
ack(U). The compiler can be instructed, by the V option, to use another size for pointer
objects. NIL is represented here by the appropriate number of zero words.
It depends on the EM implementation whether dereferencing of a pointer with value
NIL causes an error.
BS 6.5.4: It shall be an error to remove the identifying-value of an identified variable from its
pointer-type when a reference to the variable exists.
When the identified variable is an element of the record-variable-list of a
with statement, a warning is given at compile-time. Otherwise, this error is not
detected.
BS 6.5.5: It shall be an error to alter the value of a file-variable f when a reference to the
buffer-variable fˆ exists.
When f is altered when it is an element of the record-variable-list of a with-statement, a
warning is given. When a buffer-variable is used as a variable-parameter, an error is
given. This is done at compile-time.
BS 6.6.5.2: It shall be an error if the stated pre-assertion does not hold immediately prior to
any use of the file handling procedures rewrite, put, reset and get.
For each of these four operations the pre-assertions can be reformulated as:
rewrite(f): no pre-assertion.
put(f): f is opened for writing and fˆ is not undefined.
reset(f): f exists.
get(f): f is opened for reading and eof(f) is false.
The following errors are detected for these operations:
rewrite(f):
more args expected, trap 64, fatal:
f is a program-parameter and the corresponding file name is not supplied by
the caller of the program.
rewrite error, trap 101, fatal:
the caller of the program lacks the necessary access rights to create the file
in the file system or operating system problems like table overflow prevent
creation of the file.
Amoeba 5.3 51
put(f):
file not yet open, trap 72, fatal:
reset or rewrite are never applied to the file. The checks performed by the
run time system are not foolproof.
not writable, trap 96, fatal:
f is opened for reading.
write error, trap 104, fatal:
probably caused by file system problems. For instance, the file storage is
exhausted. Because IO is buffered to improve performance, it might
happen that this error occurs if the file is closed. Files are closed whenever
they are rewritten or reset, or on program termination.
reset(f):
more args expected, trap 64, fatal:
same as for rewrite(f).
reset error, trap 100, fatal:
f does not exist, or the caller has insufficient access rights, or operating
system tables are exhausted.
get(f):
file not yet open, trap 72, fatal:
as for put(f).
not readable, trap 97, fatal:
f is opened for writing.
end of file, trap 98, fatal:
eof(f) is true just before the call to get(f).
read error, trap 103, fatal:
unlikely to happen. Probably caused by hardware problems or by errors
elsewhere in your program that destroyed the file information maintained
by the run time system.
truncated, trap 99, fatal:
the file is not properly formed by an integer number of file elements. For
instance, the size of a file of integer is odd.
non-ASCII char read, trap 106, non-fatal:
the character value of the next character-type file element is out of range
(0..127). Only for text files.
BS 6.6.5.3: It shall be an error if a variant of a variant-part within the new variable becomes
active and a different variant of the variant-part is one of the specified variants.
This error is not detected.
BS 6.6.5.3: It shall be an error to use dispose(q) if the identifying variable has been allocated
using the form new(p,c1,...,cn).
This error is not detected. However, this error can cause more memory to be freed then
was allocated. Dispose causes a fatal trap 73 when memory already on the free list is
freed again.
BS 6.6.5.3: It shall be an error to use dispose(q,k1,...,km) if the identifying variable has been
allocated using the form new(p,c1,...,cn) and m is not equal to n.
This error is not detected. However, this error can cause more memory to be freed then
52 Amoeba 5.3
was allocated. Dispose causes a fatal trap 73 when memory already on the free list is
freed again.
BS 6.6.5.3: It shall be an error if the variants of a variable to be disposed are different from
those specified by the case-constants to dispose.
This error is not detected.
BS 6.6.5.3: It shall be an error if the value of the pointer parameter of dispose has nil-value
or is undefined.
The same comments apply as for dereferencing NIL or undefined pointers.
BS 6.6.5.3: It shall be an error if a variable created using the second form of new is accessed
by the identified variable of the variable-access of a factor, of an assignment-statement, or of
an actual-parameter.
This error is not detected.
BS 6.6.6.2: It shall be an error if the value of sqr(x) does not exist.
This error is detected for real-type arguments (real overflow, trap 4, non-fatal).
BS 6.6.6.2: It shall be an error if x in ln(x) is smaller than or equal to 0.
This error is detected (error in ln, trap 66, non-fatal)
BS 6.6.6.2: It shall be an error if x in sqrt(x) is smaller than 0.
This error is detected (error in sqrt, trap 67, non-fatal)
In addition to these errors, overflow in the expression exp(x) is detected (error in exp,
trap 65, non-fatal; real overflow, trap 4, non-fatal)
BS 6.6.6.3: It shall be an error if the integer value of trunc(x) does not exist.
It depends on the implementations whether this error is detected. The floating-point
emulation detects this error (conversion error, trap 10, non-fatal).
BS 6.6.6.3: It shall be an error if the integer value of round(x) does not exist.
It depends on the implementations whether this error is detected. The floating-point
emulation detects this error (conversion error, trap 10, non-fatal).
BS 6.6.6.4: It shall be an error if the integer value of ord(x) does not exist.
This error can not occur, because the compiler will not allow such ordinal types.
BS 6.6.6.4: It shall be an error if the character value of chr(x) does not exist.
Except when the R option is off, the compiler generates an EM range check instruction.
The effect of this instruction depends on the EM implementation.
BS 6.6.6.4: It shall be an error if the value of succ(x) does not exist.
Same comments as for chr(x).
BS 6.6.6.4: It shall be an error if the value of pred(x) does not exist.
Same comments as for chr(x).
BS 6.6.6.5: It shall be an error if f in eof(f) is undefined.
This error is detected (file not yet open, trap 72, fatal).
BS 6.6.6.5: It shall be an error if f in eoln(f) is undefined, or if eof(f) is true at that time.
Amoeba 5.3 53
The following errors may occur:
file not yet open, trap 72, fatal;
not readable, trap 97, fatal;
end of file, trap 98, fatal.
BS 6.7.1: It shall be an error if a variable-access used as an operand in an expression is
undefined at the time of its use.
The compiler performs some limited checks to see if identifiers are used before they are
set. Since it can not always be sure (one could, for instance, jump out of a loop), only a
warning is generated. When an expression contains a function-call, an error occur if the
function is not assigned at run-time.
BS 6.7.2.2: A term of the form x/y shall be an error if y is zero.
It depends on the EM implementation whether this error is detected. On some
machines, a trap may occur.
BS 6.7.2.2: It shall be an error if j is zero in ’i div j’.
It depends on the EM implementation whether this error is detected. On some
machines, a trap may occur.
BS 6.7.2.2: It shall be an error if j is zero or negative in i MOD j.
This error is detected (only positive j in ’i mod j’, trap 71, non-fatal).
BS 6.7.2.2: It shall be an error if the result of any operation on integer operands is not
performed according to the mathematical rules for integer arithmetic.
The reaction depends on the EM implementation. Most implementations, however, will
not notice integer overflow.
BS 6.8.3.5: It shall be an error if none of the case-constants is equal to the value of the case-
index upon entry to the case-statement.
This error is detected (case error, trap 20, fatal).
BS 6.9.1: It shall be an error if the sequence of characters read looking for an integer does
not form a signed-integer as specified in 6.1.5.
This error is detected (digit expected, trap 105, non-fatal).
BS 6.9.1: It shall be an error if the sequence of characters read looking for a real does not
form a signed-number as specified in 6.1.5.
This error is detected (digit expected, trap 105, non-fatal).
BS 6.9.1: When read is applied to f, it shall be an error if the buffer-variable fˆ is undefined
or the pre-assertions for get do not hold.
This error is detected (see get(f)).
BS 6.9.3: When write is applied to a textfile f, it shall be an error if f is undefined or f is
opened for reading.
This error is detected (see put(f)). Furthermore, this error is also detected when f is not
a textfile.
BS 6.9.3.1: The values of TotalWidth or FracDigits shall be greater than or equal to one; it
shall be an error if either value is less then one.
When either value is less than zero, an error (illegal field width, trap 75, non-fatal)
54 Amoeba 5.3
occurs. Zero values are allowed, in order to maintain some compatibility with the old
Ack-Pascal compiler.
BS 6.9.5: It shall be an error if the pre-assertion required for writeln(f) do not hold prior to
the invocation of page(f);
This error is detected (see put(f)).
External Routines
Except for the required directive forward the Ack-Pascal compiler recognizes the directive
extern . This directive tells the compiler that the procedure block of this procedure will not
be present in the current program. The code for the body of this procedure must be included
at a later stage of the compilation process.
This feature allows one to build libraries containing often used routines. These routines do
not have to be included in all the programs using them. Maintenance is much simpler if there
is only one library module to be changed instead of many Pascal programs.
Another advantage is that these library modules may be written in a different language, for
instance C or the EM assembly language. This is useful if you want to use some specific EM
instructions not generated by the Pascal compiler. Examples are the system call routines and
some floating point conversion routines. Another motive could be the optimization of some
time-critical program parts.
The use of external routines, however, is dangerous. The compiler normally checks for the
correct number and type of parameters when a procedure is called and for the result type of
functions. If an external routine is called these checks are not sufficient, because the
compiler can not check whether the procedure heading of the external routine as given in the
Pascal program matches the actual routine implementation. It should be the loader’s task to
check this. However, the current loaders are not that smart. Another solution is to check at
run time, at least the number of words for parameters. Some EM implementations check this.
For those who wish the use the interface between C and Pascal we give an incomplete list of
corresponding formal parameters in C and Pascal.
Pascal C
a:integer int a
a:char int a
a:boolean int a
a:real double a
a:ˆtype type *a
var a:type type *a
procedure a(pars) struct {
void (*a)() ;
char *static link ;
}
function a(pars):type struct {
Amoeba 5.3 55
type (*a)() ;
char *static link ;
}
The Pascal run-time system uses the following algorithm when calling function/procedures
passed as parameters.
Separate Compilation
The compiler is able to (separately) compile a collection of declarations, procedures and
functions to form a library. The library may be linked with the main program, compiled
later. The syntax of these modules is
module = [constant-definition-part]
[type-definition-part]
[var-declaration-part]
[procedure-and-function-declaration-part]
The compiler accepts a program or a module:
unit = program | module
All variables declared outside a module must be imported by parameters, even the files input
and output. Access to a variable declared in a module is only possible using the procedures
and functions declared in that same module. By giving the correct procedure/function
heading followed by the directive ‘extern’ you may use procedures and functions declared in
other units.
Assertions
When the s option is off, Ack-Pascal compiler recognizes an additional statement, the
assertion. Assertions can be used as an aid in debugging and documentation. The syntax is:
assertion = ’assert’ Boolean-expression
An assertion is a simple-statement, so
simple-statement = [assignment-statement |
procedure-statement |
goto-statement |
assertion
]
An assertion causes an error if the Boolean-expression is false. That is its only purpose. It
does not change any of the variables, at least it should not. Therefore, do not use functions
with side-effects in the Boolean-expression. If the a option is turned on, then assertions are
skipped by the compiler. However, assignment to a variable and calling of a procedure with
that name will be impossible. If the s option is turned on, the compiler will not know a thing
56 Amoeba 5.3
about assertions, so using assertions will then give a parse error.
Additional Procedures.
Three additional standard procedures are available:
halt:
a call of this procedure is equivalent to jumping to the end of your program. It is always
the last statement executed. The exit status of the program may be supplied as optional
argument. If not, it will be zero.
release:
mark:
for most applications it is sufficient to use the heap as second stack. Mark and release
are suited for this type of use, more suited than dispose. mark(p), with p of type pointer,
stores the current value of the heap pointer in p. release(p), with p initialized by a call
of mark(p), restores the heap pointer to its old value. All the heap objects, created by
calls of new between the call of mark and the call of release, are removed and the space
they used can be reallocated. Never use mark and release together with dispose!
Amoeba Interfacing.
If the c option is turned on, then some special features are available to simplify an interface
with the Amoeba environment. First of all, the compiler allows you to use a different type of
string constants. These string constants are delimited by double quotes (‘"’). To put a double
quote into these strings, you must repeat the double quote, like the single quote in normal
string constants. These special string constants are terminated by a zero byte (chr(0)). The
type of these constants is a pointer to a packed array of characters, with lower bound 1 and
unknown upper bound.
Secondly, the compiler predefines a new type identifier ’string’ denoting this just described
string type.
The only thing you can do with these features is declaration of constants and variables of type
’string’. String objects may not be allocated on the heap and string pointers may not be
dereferenced. Still these strings are very useful in combination with external routines. The
procedure write is extended to print these zero-terminated strings correctly.
Amoeba 5.3 57
for longs is 11. The standard procedures ’abs’ and ’sqr’ have been extended to work on long
arguments. Conversion from integer to long, long to real, real to long and long to integer are
automatic, like the conversion from integer to real. These conversions may cause a
conversion error, trap 10, non-fatal
Underscore As Letter
The underscore character ’ ’ may be used in forming identifiers, if the u or U option is turned
on. It is forbidden to start identifiers with underscores, since this may cause name-clashes
with run-time routines.
Pre-processing
If the very first character of a file containing a Pascal program is the sharp (’#’, ASCII
23(hex)) the file is preprocessed in the same way as C programs. Lines beginning with a ’#’
are taken as preprocessor command lines and not fed to the Pascal compiler proper. C style
comments, /*......*/, are removed by the C preprocessor, thus C comments inside Pascal
programs are also removed when they are fed through the preprocessor.
2. The standard procedures read, readln, write and writeln are implemented as word-
symbols, and can therefore not be redeclared.
Compiler Options
Some options of the compiler may be controlled by using ‘‘{$....}’’. Each option consists of
a lower case letter followed by +, − or an unsigned number. Options are separated by
commas. The following options exist:
a +/− this option switches assertions on and off. If this option is on, then code is included
58 Amoeba 5.3
to test these assertions at run time. Default +.
c +/− this option, if on, allows you to use C-type string constants surrounded by double
quotes. Moreover, a new type identifier ’string’ is predefined. Default −.
d +/− this option, if on, allows you to use variables of type ’long’. Default −.
i <num> with this flag the setsize for a set of integers can be manipulated. The number must
be the number of bits per set. The default value is wordsize−1.
l +/− if + then code is inserted to keep track of the source line number. When this flag is
switched on and off, an incorrect line number may appear if the error occurs in a
part of your program for which this flag is off. These same line numbers are used
for the profile, flow and count options of the EM interpreter em . Default +.
r +/− if + then code is inserted to check subrange variables against lower and upper
subrange limits. Default +.
s +/− if + then the compiler will hunt for places in your program where non-standard
features are used, and for each place found it will generate a warning. Default −.
t +/− if + then each time a procedure is entered, the routine procentry is called, and each
time a procedure exits, the procedure procexit is called. Both procentry and
procexit have a string as parameter. This means that when a user specifies his or
her own procedures, the c option must be used. Default procedures are present in
the run-time library. Default −.
u +/− if + then the underscore character ‘‘ ’’ is treated like a letter, so that it may be used
in identifiers. Procedure and function identifiers are not allowed to start with an
underscore because they may collide with library routine names. Default −.
Some of these flags (c, d, i, s, u, C and U) The others may be switched on and off.
A very powerful debugging tool is the knowledge that inaccessible statements and useless
tests are removed by the EM optimizer. For instance, a statement like:
if debug then
writeln(’initialization done’);
is completely removed by the optimizer if debug is a constant with value false. The first line
is removed if debug is a constant with value true. Of course, if debug is a variable nothing
can be removed.
A disadvantage of Pascal, the lack of preinitialized data, can be diminished by making use of
the possibilities of the EM optimizer. For instance, initializing an array of reserved words is
sometimes optimized into 3 EM instructions. To maximize this effect you must initialize
variables as much as possible in order of declaration and array entries in order of decreasing
index.
References
[1] BSI standard BS 6192: 1982 (ISO 7185).
[2] A.S.Tanenbaum, J.W.Stevenson, Hans van Staveren, E.G.Keizer, Description of a
machine architecture for use with block structured languages, Informatica rapport IR-
81.
Amoeba 5.3 59
5.1.5. ACK Modula-2 Compiler Compliance Statements
Introduction
This document describes the implementation-specific features of the ACK Modula-2
compiler. It is not intended to teach Modula-2 programming. For a description of the
Modula-2 language, the reader is referred to [1].
The ACK Modula-2 compiler is currently available for use with the VAX, Motorola
MC680x0 and Intel 80x86 code-generators. For the 80x86, and MC680x0, floating point
emulation is used. This is made available with the –fp option, which must be passed to ack.
Syntax (section 2)
The syntax recognized is that of the Report, with some extensions to also recognize the
syntax of an earlier definition, given in [2]. Only one compilation unit per file is accepted.
60 Amoeba 5.3
still may not start an identifier.
Constants of type LONGINT are integers with a suffix letter D (for instance 1987D).
Constants of type LONGREAL have suffix D if a scale factor is missing, or have D in place of
E in the scale factor (for example, 1.0D, 0.314D1). This addition was made, because there
was no way to indicate long constants, and also because the addition was made in Wirth’s
newest Modula-2 compiler.
Amoeba 5.3 61
Set Types (section 6.6)
The only limitation imposed by the compiler is that the base type of the set must be a
subrange type, an enumeration type, CHAR, or BOOLEAN. So, the lower bound may be
negative. However, if a negative lower bound is used, the compiler gives a warning of the
restricted class (see the manual page of the compiler).
The standard type BITSET is defined as
TYPE BITSET = SET OF [0 .. 8*SIZE(INTEGER)-1];
Expressions (section 8)
Statements (section 9)
62 Amoeba 5.3
Case Statements (section 9.5)
The size of the type of the case-expression must be less than or equal to the word-size.
The Report does not specify what happens if the value of the case-expression does not occur
as a label of any case, and there is no ELSE-part. In our implementation, this results in a
run-time error.
Amoeba 5.3 63
Backwards Compatibility
Besides recognizing the language as described in [1], the compiler recognizes most of the
language described in [2], for backwards compatibility. It warns the user for old-fashioned
constructions (constructions that [1] does not allow). If the –Rm2–3 option is passed to ack,
this backwards compatibility feature is disabled. Also, it may not be present on some smaller
machines, like the PDP-11.
Compile-Time Errors
The compile-time error messages are intended to be self-explanatory, and not listed here.
The compiler also sometimes issues warnings, recognizable by a warning-classification
between parentheses. Currently, there are 3 classifications:
(old-fashioned use)
These warnings are given on constructions that are not allowed by [1], but are allowed
by [2].
(strict)
These warnings are given on constructions that are supported by the ACK Modula-2
compiler, but might not be supported by others. Examples: functions returning
structured types, SET types of subranges with negative lower bound.
(warning)
The other warnings, such as warnings about variables that are never assigned, never
used, etc.
Run-time Errors
The ACK Modula-2 compiler produces code for an EM machine. Therefore, it depends on
the implementation of the EM machine for detection some of the run-time errors that could
occur.
The Traps module enables the user to install his own run-time error handler. The default one
just displays what happened and exits. Basically, a trap handler is just a procedure that takes
an INTEGER as parameter. The INTEGER is the trap number. This INTEGER can be one
of the EM trap numbers, or one of the numbers listed in the Traps definition module.
The following run-time errors may occur:
array bound error
The detection of this error depends on the EM implementation.
range bound error
Range bound errors are always detected, unless run-time checks are disabled.
set bound error
The detection of this error depends on the EM implementation. The current
implementations detect this error.
integer overflow
The detection of this error depends on the EM implementation.
cardinal overflow
64 Amoeba 5.3
This error is detected, unless run-time checks are disabled.
cardinal underflow
This error is detected, unless run-time checks are disabled.
real overflow
The detection of this error depends on the EM implementation.
real underflow
The detection of this error depends on the EM implementation.
divide by 0
The detection of this error depends on the EM implementation.
divide by 0.0
The detection of this error depends on the EM implementation.
undefined integer
The detection of this error depends on the EM implementation.
undefined real
The detection of this error depends on the EM implementation.
conversion error
This error occurs when assigning a negative value of type INTEGER to a variable of
type CARDINAL, or when assigning a value of CARDINAL, that is >
MAX(INTEGER), to a variable of type INTEGER. It is detected, unless run-time
checking is disabled.
stack overflow
The detection of this error depends on the EM implementation.
heap overflow
The detection of this error depends on the EM implementation. Might happen when
ALLOCATE fails.
case error
This error occurs when non of the cases in a CASE statement are selected, and the
CASE statement has no ELSE part. The detection of this error depends on the EM
implementation. All current EM implementations detect this error.
stack size of process too large
This is most likely to happen if the reserved space for a coroutine stack is too small. In
this case, increase the size of the area given to NEWPROCESS. It can also happen if the
stack needed for the main process is too large and there are coroutines. In this case, the
only fix is to reduce the stack size needed by the main process, for example, by avoiding
local arrays.
too many nested traps + handlers
This error can only occur when the user has installed his own trap handler. It means
that during execution of the trap handler another trap has occurred, and that several
times. In some cases, this is an error because of overflow of some internal tables.
no RETURN from function procedure
This error occurs when a function procedure does not return properly (‘‘falls through’’).
illegal instruction
This error might occur when you use floating point operations on an implementation
Amoeba 5.3 65
that does not have floating point.
In addition, some of the library modules may give error messages. The Traps-module has a
suitable mechanism for this.
References
[1] Niklaus Wirth, Programming in Modula-2, third, corrected edition, Springer-Verlag,
Berlin (1985)
[2] Niklaus Wirth, Programming in Modula-2, Stringer-Verlag, Berlin (1983)
66 Amoeba 5.3
6 Programming Tools
This chapter contains the reference manuals for the programming tools provided with
Amoeba. Each section describes one tool. There are several important tools described. The
first is the Amoeba Interface Language (AIL). This is a stub compiler for generating servers
and their client stubs. The second is amake which is a parallel make program which given a
list of sources, the name and the type of the target and a set of generation tools will create the
desired target. The third is bawk which is an implementation of the AWK language.
Amoeba 5.3 67
6.1. The Amoeba Interface Language (AIL) Reference Manual
6.1.1. Overview
AIL is a tool to simplify and standardize the generation of RPC interfaces for Amoeba.
Widespread use of AIL in Amoeba will more or less enforce certain conventions, such as the
interpretation of header fields and the private parts of a capability, that are not enforced by
the Amoeba kernel.
AIL is a stub compiler that generates the glue between an Amoeba server and a client
program from an interface specification. The outputs it can currently generate are header
files that client programs should include if they use the corresponding interface, client stubs
that should be linked with the client program, a server mainloop that decodes the Amoeba
message and calls the corresponding implementation routine for the requested remote
procedure call and marshaling routines. All output is currently written in C. It should not be
too difficult to modify AIL to generate output in any procedural language. AIL-generated
stubs pass minimal information about the architecture of the client, such as the byte order.
No information about user defined types is passed.
One could also regard AIL as a language in which one can express types and calling
conventions (i.e., the syntax) of a procedure in a language independent way. The semantics
of a procedure are not expressed in AIL. Procedures are grouped in classes, which in turn
can inherit the procedures of other classes.
After defining procedures, types and their grouping one can generate translations into various
languages. The mechanisms to generate these translations are not described here. The fact
that AIL is a stub compiler should, according to this view, not be mentioned in this
document, since it results from the fact that it contains translators for this purpose. This view
was not adopted in this document, since it forces the user to read between the lines too much.
Notwithstanding, the exact documentation of AIL’s output is in a separate document: the
AIL Cookbook.
Types
Currently, AIL supports all of the C-types, in that it can parse and comprehend them, but for
some types AIL cannot generate marshaling routines automatically. They are: void,
functions, pointers, enums and unions. If you insist on passing these, you can tell AIL to use
your own, handwritten marshaling code. The type array has been extended, but a simple C-
style array has the same meaning as in C. Most types have marshaling routines and size
expressions associated with them. If they do not, AIL is incapable of generating code to pass
it to a remote procedure. There is an important difference between the type systems of C and
AIL. A typedef yields the same type as the type it is built from, except for the marshaling
and size information. Thus, to marshal integers differently, make a typedef ‘‘myint’’, and
specify the new marshaling and size information.
Procedures
In this document the word procedure is used to mean the call convention of an operation. A
procedure syntactically resembles a STD C function prototype.
Usually it has a magic first argument represented as ‘‘*’’. In this case it defines both client
68 Amoeba 5.3
and server stub, or more precisely, it defines the format of both the request and reply
messages, from which the stubs can be generated. A number to be used as h command value
is picked from a range that is specified together with the class the procedure is defined in.
The client stub is called after the procedure. The server stub does not need a name, because
it is not implemented as a function. The third routine associated with a procedure is the
implementation, which should be written by every server implementor who wants to
implement the class in which the procedure is defined. The name of an implementation is the
name of the procedure, prefixed with impl . An implementation has the same function
prototype as the procedure where the type of the first argument, the ‘‘*’’ of the procedure, is
implementation dependent. Currently it is always a pointer to a header.
If the ‘‘*’’ is absent, AIL still assumes that it describes a function that is logically part of the
interface. An example is the function dir home , which does not use trans, but should
obviously be part of the directory interface. Other star-less procedures might be handwritten
wrappers around a client stub. Thus, no command numbers or implementation routines are
associated with these procedures. The assumption is that the basic RPC format will have to
be published anyway, since that is Amoeba’s basic communication primitive.
Classes
A class groups together constant, type, marshaling, and procedure definitions. It can inherit
the constants, types and procedures of other classes. Usually an integer range is specified, to
allow the definition of procedures with the magic first star. AIL uses this range to pick a
value for the h command value. If the range is absent in the class definition, the class still
represents a logical grouping of procedures, constants and types.
The interface is specified in an object-oriented style. This eases the development of servers
that use standard interfaces. Let us suppose there is a class called std io that holds the read
and the write operators. A programmer that is building a server that knows about reading and
writing its object, but also about destroying objects, simply defines a class that inherits the
std io class, and adds the destroy procedure. Of course, the programmer will have to provide
the implementation of all the operators, including the inherited ones, but the old library
routines for the read and write interface can be used.
AIL implements multiple inheritance. This allows the programmer to support an arbitrary
number of classes, as long as their h command ranges and the names defined in the classes
are disjunct.
For each class AIL can generate a server-mainloop. The mainloop contains a big switch,
surrounded by a getreq and a putrep. The cases in the switch are called server stubs. The
task of a server stub is to get the arguments of a procedure, and call the corresponding
implementation function, which should be supplied by the interface designer. The server
mainloop contains such a server stub for every procedure that is defined or inherited by the
class.
Amoeba 5.3 69
6.1.2. Language Specification
Scope rules
Identifiers declared within a class C are visible after their declaration, and in classes that in-
herit the class C. There is no concept of qualifiers as in Modula-2. This implies that if two
separate classes declare identifier foo, it is impossible to inherit both in another class. Any-
thing defined outside a class is defined in a global scope. These names are visible in classes
that do not redefine the identifier.
The above rules do not apply exactly to union, enum, and structure tags. As in C, these al-
ways have a global scope. They are also the only exception to the rule that an identifier can-
not be redeclared in the same scope. Declarations like:
typedef struct foo {
struct foo *next;
...
} foo;
are allowed, and define struct foo in the global scope, and foo in the local one − which may
be the global scope.
Syntax specification
Wherever the syntax of C was useful it was decided to stick with it. This mainly refers to
type-declarations and expressions, but some other syntax constructs look C-ish too.
Notation used
The following notation is used in the presentation of the grammar:
Foo : ... A rule defining the meaning of Foo
Foo (capitalized words) nonterminals in the grammar
FOO (all upper case) terminal symbols
’foo’ (quoted strings) literal terminal symbols
() used for grouping
Foo* zero or more times Foo
Foo+ one or more times Foo
[Foo] zero or one times Foo
{Foo DELIM} one or more times Foo, separated by DELIM if more than one
Foo | Bar separates alternatives
70 Amoeba 5.3
| MarshalDefinition
TypeConstructor : IDENT
| ’(’ TypeConstructor ’)’
| ’*’ TypeConstructor
| TypeConstructor ’[’ [ActualBound [ ’:’ MaximumBound] ’]’
| TypeConstructor ’(’ ’)’
ActualBound : Expression
MaximumBound : Expression
LocalDefinition : ConstantDefinition
| TypeDefinition
| OperationDefinition
| MarshalDefinition
| RightsDefinition
ConstantDefinition : ’const’ IDENT ’=’ ConstantExpression ’;’
| ’const’ IDENT ’=’ STRING ’;’
Amoeba 5.3 71
MarshInfo : ’with’ [ ActualSize [ ’:’ MaximumSize ] ]
[ ’in’ [ClientMarshal] ’,’ [ServerUnmarshal] ]
[ ’out’ [ClientUnmarshal] ’,’ [ServerMarshal] ]
ClientMarshal : IDENT
ClientUnmarshal : IDENT
ServerMarshal : IDENT
ServerUnmarshal : IDENT
The identifiers are supposed to be marshaling routines. They are called with a pointer in the
transaction buffer, and the value they are supposed to marshal. They must return a pointer to
where they left off. Server marshalers might get an extra parameter, indicating the
architecture of the client. Currently, the most realistic way to learn the prototype of a
marshaler is forcing AIL to write code that needs to call the marshaler, regrettably.
ActualSize : [’const’] Expression
MaximumSize : [’const’] Expression
ConstantExpression : Expression
Expression : Expression DyadicOptr Expression
| UnaryOptr Expression
| ’(’ Expression ’)’
| FunctionCall
| NUMBER
| IDENT
72 Amoeba 5.3
Expressions
Expressions are modeled after C expressions. Some operators are missing. The remaining
ones are listed below in precedence order:
|| denotes logical OR.
&& denotes logical AND.
| denotes bitwise inclusive OR.
ˆ denotes bitwise exclusive OR.
& denotes bitwise AND.
== and != denote equal and unequal.
>, <, >= and <= test for greater than, smaller than, greater
or equal, and smaller or equal respectively.
<< and >> are the bitshift operators.
+ and − denote addition and subtraction respectively.
*, / and % denote multiplication, division and modulo.
Unary operators, which are all prefix, have the highest precedence. They are:
‘‘+’’, ‘‘−’’, ‘‘ ’’ and ‘‘!’’,
which mean nothing, unary minus, bitwise NOT and logical NOT. Parentheses can be used
to override the precedence rules. Primary expressions are constants, identifiers and function
calls. The identifiers in an expression must refer to an integer constant. Effectively, only
integer expressions are allowed.
Lexical analysis.
Lexical analysis is the same as for C, except for the keywords. The input source is first
preprocessed by the C preprocessor, then tokenized. The symbol AIL is predefined by the
preprocessor to facilitate conditional compilation. Comments, spaces and tabs between
tokens are ignored; within tokens they are illegal (except in strings, where they are
meaningful). Comments are placed between /* and */. Comments do not nest. Comments
are not recognized within strings, and vice versa.
The definitions below reflect the tokenization process. In the case of ambiguity the rule
listed first is used. The longest production of a rule is always taken. Spaces are significant
here!
IDENT : Letter [LetterOrOther]*
LetterOrOther : Letter | Digit | ’ ’
Amoeba 5.3 73
STRING : ’"’ CharOrEscape* ’"’
CharConstant : Quote CharOrEscape Quote
CharOrEscape : any character except \n, \, or the surrounding quote
| ’\n’ | ’\b’ | ’\f’ | ’\t’ | ’\r’
| ’\\’ Digit [Digit [Digit]]
| ’\\’
| ’\"’
| ’\’ Quote
The keywords are: char, class, const, double, enum, float, generate, in, include, inherit, int,
long, marshal, out, short, signed, struct, typedef, union, unsigned, var, void, with.
Types
The primary types of AIL are int, char, float, double and void. The meaning of int can be
modified using long, short, signed and unsigned. Unlike most C implementations, AIL thinks
a long long int is legal, and will feed this to your C compiler if you use one. An int and a
long are four byte quantities on the network. A short is two bytes. For floats and doubles, no
network format is defined yet. New types can be created by typedef, struct, union and enum,
which are passed as integers. Arrays, pointers and functions are like in C, though neither
pointers nor functions can be passed to procedures. There is an extended array type, which
differentiates between an actual and a maximum size. Currently, the maximum must be
constant. When transmitting such an array, the actual size is computed, and only that part of
the array is transmitted.
Classes
A ClassRange defines an integer range from which AIL can pick command values. The
classes mentioned in an InheritanceList are explicitly inherited. Any classes inherited by
these classes are implicitly inherited. This difference is only relevant to some generators.
The names defined in the inherited classes are visible in the current class. The IncludeList is
used to tell AIL which header files must be included by the client interface translation. A
string starting with ‘‘<’’ and ending in ‘‘>’’ will be translated to an #include <stdio.h> style
include, other strings are copied as is.
74 Amoeba 5.3
Procedures and parameters
Procedures define the prototype of a client stub. The implementation may have quite a
different prototype, depending on other features of AIL you use in the generate clause for the
server. The parameters to a procedure are identifiers with a type, and have certain attributes
associated with them. The most obscure attribute specifies in which field of the Amoeba
message header the parameter should travel. This attribute is there so that we can specify old
client stubs in AIL. It is called the HeaderAddress .
The other attributes define in which direction the parameter values travel, and in which way
they are passed to both client stub and implementation routine. They are in, out, and var.
The attribute var specifies that the parameter is passed by reference to both client stub and
implementation routine.
The attribute in specifies that the parameter must be shipped in the request message.
Finally the attribute out means it must be shipped in the reply message. The attribute out
implies var. If neither in nor out is specified, in is assumed. In effect, there are four
possible combinations: in, var-out, var-in, and var-in-out.
The value of an out parameter after a communication failure is undefined. Its contents might
have been overwritten during the processing of the reply by the Amoeba kernel.
AlternateNumbers are really a hack. Their use is not encouraged, and seldom necessary.
They introduce numbers that should also be recognized by a server. Only the primary
number is used in client stubs.
Amoeba 5.3 75
6.2. The AIL Cookbook
Introduction
This manual describes the parts of AIL that are target language specific: the code generators.
This documentation is in a separate place since it is probably the fastest changing part of
AIL. In particular, whenever support for a new language is added, this manual should be
changed accordingly. It is assumed that the reader is familiar with the material in the AIL
Reference Manual.
First of all, check that this documentation is consistent with the installed version of AIL.
Type ail –gG to obtain a list of code generators that the installed version of AIL knows.
By convention, the generators that affect the behavior of other generators have capitalized
names. An example of such a generator is Language, which sets the output language of AIL.
Information like this is localized by resetting it to its default every time a new generate
clause shows up.
Some code generators might have names that start with an underscore, indicating that they
should not normally be used for some reason. Some may do things that are only useful when
developing a new version of AIL, others may not be of astonishing quality, or are scheduled
to disappear. They are listed here merely for completeness.
A concise description of the syntax to call a code generator is found in The AIL Reference
Manual. As long as AIL does not support programming languages other than C, it is not
necessary to document the obvious: where ‘‘header file’’ is written it means a C-file
intended to be included whose name ends in ‘‘.h’’, and where it says ‘‘function’’ it refers to
one written in C.
All code generators that create files put them in the output directory set by the command line
−o option. The default output directory is the working directory. If the output directory is
absent, it will be created.
The rest of this document describes the following code generators: Act deact, class list,
client interface, client stubs, command, complete interface, complete stubs, contents,
define, feature, flat interface, Get mem, Idempotent, impl dummy, Impl repl buf,
Language, marsh funcs, Monitor client, ordinal, output files, Output directory,
Pass acttype, Post deact, server, type list
76 Amoeba 5.3
variable of type type-name. The deactivate function is of type void, and optionally has one
argument of type type-name :
errstat activate function(private * prv);
void deactivate function(void);
As mentioned above, obj parameters of type type name * and type name respectively are
passed if generator Pass acttype was used in the same generate clause.
After the request comes in, the activate function is called. If it returns something other than
STD OK, no implementation function is called, and the server returns the error status to the
client. If it returns STD OK, the activate function is supposed to have set * obj. The
implementation function is called, and then the deactivate function will be called with the
variable set by the activate function, whether the implementation succeeded or failed.
See also : Pass acttype, server, Post deact.
Amoeba 5.3 77
The function is written in the flavor of C selected by generator ‘‘Language’’, or the default
language if none was selected.
AIL assumes that operators that do not have the special first argument ‘‘*’’ will be provided
by the programmer. A request to write a complete client stub cannot be satisfied anyway,
because AIL would not know which port to use. Instead, it will generate a dummy function
that has the parameter list AIL expects it to have. It returns an error code right away. In this
case the file is called <operator-id>.c.dummy.
See also : complete stubs, Get mem, Idempotent, Language, Monitor client.
Generator command
This generator generates one or more stand alone programs that do one transaction. The
arguments to the generator are the names of the operators for which a program should be
created. If invoked without arguments, AIL will generate a program for each operator
defined in the class. Each program appears in a separate file, called cmd <operator-name>.c,
e.g., cmd op.c for operator op.
The program, when compiled and executed, expects the in-parameters of the operator as
command line arguments. The first argument is converted to a capability using
name lookup(L). The out-parameters and their values are printed on stdout, except for any
out capabilities. These are registered at the directory server using a path from the command
line. Any previous entry with the same name is deleted, but not destroyed. If any error
occurs, the program prints the most descriptive message possible given AIL’s limited
knowledge of what it is doing, and exits with status 1. Hint: the first error to check is the
number of parameters, which is fixed. Since these programs take at least one argument (the
object capability), invoking it without arguments makes it print a usage string.
Currently, only integers, capabilities and character arrays are accepted as arguments. The
way the latter are handled is a genuine bug, which will be around until AIL knows about
strings. By then, the support for character arrays will probably disappear.
See also : Language, Output directory.
78 Amoeba 5.3
Generator contents
Lists the contents of a class on stdout.
Generator define
This generator creates a header file that defines the h command values it picked for each
operator. To mimic C programmers, it folds the names of the operators to uppercase. The
header file is called <class> def.h.
Generator feature
This generator generates several miscellaneous things. They are put together in a single file
called <class-id> feature.c .
The argument def inh tells it to emit the declaration and initialization of a character pointer
array called def inh. These pointers point to the names of the classes in the inherit
statement. The last pointer in the array is initialized to null. The argument eff inh creates a
similar array called eff inh, containing the names of the effectively inherited classes. The
first class listed is the superclass.
Generator Idempotent
Usage:
Idempotent( [retry = expr, ] operators );
If an operator is declared to be idempotent, AIL will generate client stubs that retry <expr>
times if trans returns RPC FAILURE. The current default for retry is five. Note that trans
is called at most retry + 1 times.
Please also note that the return value of a stub generated like this is the value for the last
attempt. This means that RPC NOTFOUND does not guarantee that the operation was never
Amoeba 5.3 79
executed.
See also : client stubs.
Generator Language
This generator tells AIL which language should be generated. It takes one argument, which
stands for the language. Currently, it knows about traditional, ansi and lint. All are C
dialects. The default is traditional.
Not all generators actually obey this setting, because it does not make sense for some.
80 Amoeba 5.3
Generator ordinal
For tagged enumerated types, AIL can generate conversion routines from enumerated value
to enumeration identifier and back. The identifiers are ordinary, statically allocated strings.
The table of strings is called tab <tag> . The conversion routine from enumerated type to
string is called str <tag> , and the conversion back is called ord <tag> . They are to be
found in the file conv <tag>.c . The type of tab <tag> is explicitly left undocumented, as is
the behavior of the ord <tag> function when invoked with an invalid parameter.
The functions are written in the flavor of C selected by generator ‘‘Language’’, or the default
language if none was selected.
See also : Language.
Generator server
Used to create server main loops. The server loop is put in the file ml <class-id>.c . Some
aspects of the server loop can be modified by passing optional parameters.
Specifying rights check has the effect that the server will verify that a capability
contains the appropriate rights for an operation before calling the implementation. Note
that the generated code is insufficient to make a server secure. The capability should be
Amoeba 5.3 81
checked for the correct check field as well. Hint: this can be done in an activate
function provided by the programmer. See Act deact .
Specifying monitor has the effect that monitor.h is also included. See monitor (L) for
the implications of this.
Another argument is buf size, which should have a positive value, because it specifies
the size of the message buffer.
Another way to provide a message buffer is passing it at run-time, which is indicated by
the parameter my buf. This results in a function that expects you to pass a port pointer,
a buffer pointer and a buffer size, in that order. The buffer size is declared as a bufsize .
Currently it is an unsigned 16-bit integer. This will probably change in Amoeba 6.0.
If neither buf size or my buf is specified, AIL is responsible for the buffer size.
However, if any of the supported operators has a variable sized parameter, AIL does not
succeed. It simply prints a warning and comes up with a compiled-in default.
The argument no loop indicates that no loop should be generated. In this case the
generated routine handles at most one request and returns zero if all went well.
The server loop looks like this:
First of all, ailamoeba.h and the client interface are included. The latter should be in the file
<class-id>.h . Then the marshaling functions are declared (maybe these should be part of the
client interface?). Since C programmers might want to implement a marshaling function as
a macro, every declaration is surrounded by #ifdef/#endif’s. This is used by ailamoeba.h to
speed some things up, by the way. The server main loop is implemented as a function called
ml <class-id> . It takes one argument unless my buf was passed: a port-pointer. Unless
no loop is passed, the function does repeated calls to getreq on this port, and only returns if
getreq fails. The return value is the return value from getreq is this case.
It decodes the request and calls the corresponding implementation function if all is well.
This implementation function is supposed to have a name of the form impl <operator-id> ,
so the implementation for std info is impl std info.
The return value of the implementation is treated as an error code. It should be zero if all is
well, and is shipped in the h command field, which is #defined to h status.
If the implementation function terminates successfully, the results are marshaled in a
message buffer. After that, putrep is called.
See also : Act deact, Impl repl buf, Language, Pass acttype, Post deact.
82 Amoeba 5.3
6.3. Amake User Reference Manual
6.3.1. Introduction
Amake is a software configuration manager that was designed to be a tool in the same spirit
as make , i.e., it is a stand-alone tool that, when run by the user, should invoke precisely those
commands that (re-)create a set of target files, according to a description file. The main idea
is to make use of previous results as much as possible. Unlike standard make , amake is also
able to exploit possible parallelism between the commands. Although, as the name suggests,
amake was designed to be used on the Amoeba distributed operating system, it also runs
under UNIX.
In contrast to a lot of ‘‘extended makes’’ the specification language is not a superset of
make ’s, but a totally new one. This paper focuses mainly on the syntax and semantics of the
various constructs in the language. To make it as flexible as possible, amake has no built-in
knowledge about specific languages or tools. The presence and usage of tools available can
be declared in files that serve as a library of site-specific amake information. In order to
make it possible to exchange a software configuration between various sites easily, a
standard tool library is provided.
Amoeba 5.3 83
date (e.g., when a source file is added or moved).
As the aspects mentioned are fundamental to make , it was decided not try to implement yet
another extended version of the existing program.
6.3.3. Overview
In amake , a software configuration can be specified by means of one or more cluster
definitions. A cluster defines the targets to be constructed from a given set of sources. It is
amake ’s task to deduce which tools to use, in what order, and how to do it efficiently. The
tools available can be defined by the user, but generally a reference to a common tool library
(by means of a source inclusion mechanism) will suffice. In order to be able to decide if
some file may be used or produced by a certain tool, each file is represented internally as an
object with a set of valued attributes . (One possible value is ‘‘unknown,’’ indicating the
absence of the attribute.)
A tool definition, which shows some resemblance to a function definition found in common
programming languages, contains in its header references to the required values of certain
attributes, such as ‘‘type.’’ Rather than letting the user specify the values of all the attributes
needed, these values can most of the time be derived by amake itself, using attribute
derivations . A well-known example is the name-based derivation: a file having suffix ‘‘.c’’
is assumed to be a C source file (hence having a type attribute with value C-src), unless
explicitly specified otherwise. The rules defining derivations possible will generally be
defined in a library, together with the tools that refer to the attribute values in question, but
the user may of course provide additional ones. In derivations, as in each amake construct
that allows expressions, built-in functions and previously declared tools may be used.
6.3.4. Example
For an illustration of the major differences between make and amake consider the simple
compiler comp to be built from the source files parse.y , lex.l , comp.c , defs.h and the library
ident.a . A make specification for this configuration is presented in figure 6.1.
Note that intermediate files stemming from the application of an implicit rule are mentioned,
rather than the sources themselves. This may introduce portability problems, because on one
system the C-compiler might produce loadable objects (‘‘.o files’’), while on another it may
generate assembly (‘‘.s’’) files. As there is no way to specify an implicit rule combining
84 Amoeba 5.3
several files (possibly of different kinds as well), an explicit rule has to be provided in the
manner shown on lines 4 and 5.
Also note the way implicit inputs have to be specified in make . Implicit inputs are source
files that are read by the tool, in addition to the prime source file. In the example the files
read by the C-compiler (or C-preprocessor) as a result of ‘‘#include’’ directives are implicit
inputs. When one of the implicit files used in the construction of an intermediate file has
changed, the associated command has to be re-invoked because it might deliver new results
(i.e., when defs.h is modified, both parse.c and scan.c have to be recompiled). In make this
implicit dependency has to be specified explicitly, as is done on line 7.
To contrast with amake , consider the amake specification of the same configuration in figure
6.2.
1 %include std-amake.amk;
2
3 %cluster
4 {
5 %targets comp[type = program];
6 %sources parse.y, scan.l, comp.c, defs.h, ident.a;
7 };
The %include directive on line 1 causes amake to read the file ‘‘std-amake.amk’’, which
contains a standard tool set constituting a C programming environment (containing a C-
compiler, loader, library maintainer, Yacc, Lex, Lint, etc.). The cluster definition itself is
clear, apart from the phrase ‘‘[type = program]’’ on line 3, which takes care of setting the
attribute ‘‘type’’ of file comp (the target to be constructed) to ‘‘program’’. Amake needs this
information, because, in general, various kinds of objects may be produced from a given set
of sources (e.g., loadable objects may be combined to form either a runnable program or a
library). In this example, amake is able to derive the type attribute of all source objects,
using only suffixes.
A set of clauses describing implicit inputs is not needed in amake , because tools with an
implicit input concept are supposed to deliver a list of them on a report file. If the tool is not
able to do that, it is possible to specify in the tool’s definition that another program (such as
mkdep for C-programs) should be called to deliver the information needed. Amake stores the
set of actual inputs encountered when the tool was run in a (hidden) statefile, so the user is
not bothered with keeping this information up-to-date.
Concluding, it is felt that amake ’s way of describing software configurations is more
convenient for the user, less system dependent, and — thanks to the automated implicit input
and compilation flag administration — more secure.
Amoeba 5.3 85
6.3.5. Lexical elements
The lexical elements are described first, before turning our attention to the various language
constructs, one by one. The main difference between the syntax of the language used in
amake description files and the ones used in common programming languages is the fact that
keywords rather than (file name or string) constants are quoted (prefixed with the character
‘‘%’’ in this case). Figure 6.3 shows the keywords of the amake specification language.
It should be noted that a large portion of the keywords deals with the construction of tool
descriptions. Most people will just use an already existing tool set, so in practice only a
small subset of the language will be encountered.
Literals, which may be used to serve as a string constant, or as (a component of) a file name,
are sequences of upper- and lower case letters, digits, dots (‘‘.’’), minus-signs (‘‘−’’) and
underlines (‘‘ ’’). When a string containing special (but printable) characters needs to be
specified, it has to be quoted by adding apostrophes (‘‘´’’) to the beginning and end of it.
Apostrophes contained within a quoted string have to be doubled, so the string I’m ready has
to be specified as ’I’’m ready’.
The names of global variables and parameters closely resemble literals, except that they may
not contain dots or minus-signs. A reference to a previously defined parameter or variable
can be made by prefixing the name with a dollar-sign (‘‘$’’). Note that in the declaration of
a parameter or variable no dollar-sign is used, as is the case for the Bourne shell and make .
The operators and delimiters composed of special characters are shown in figure 6.4.
= == <> => + ?
& : , ; / \
( ) { } [ ]
Comments are introduced with the (unquoted) character ‘‘#’’ and continue till the end of the
line. White space (spaces, tabs and newlines) is not significant, other than as a means to
separate tokens.
86 Amoeba 5.3
6.3.6. Amake Constructions
The most important constituents of an amake ‘‘program’’ are the cluster and tool definitions,
and the attribute derivation rules. They describe, respectively, the software configurations to
be managed, the tools available, and how some attribute of an object could be computed, if it
were still unknown. These basic constructs are used as an information base in the process of
determining what actions amake has to perform, when updating a configuration. Although
they may be considered as declarative language constructs (like facts in logic programs), the
order of definition is sometimes significant, e.g., derivations rules are tried in the order in
which they are declared.
Formally, at the highest level an amake specification file has the structure shown in figure
6.5. Note that each amake-def must be followed by a semicolon.
In order to make it possible to share data between several constructs within a description file
(e.g., clusters having partly the same sources) and among independent description files (a
common tool library, for instance) there are also some imperative constructs available. They
include the assignment, source code inclusion, conditional definition, a facility to create and
instantiate generic pieces of amake code, and environment interaction.
Almost all top-level constructs may contain general expressions. In order to avoid getting
too technical right away, expressions will be defined more formally in a later section. We
will first go through the top-level language elements available.
6.3.7. Clusters
Clusters are used to describe a configuration of sources and targets that have to be created
from the former. It is amake ’s task to determine which tools it has to apply to achieve this.
If there is more than one way to do this — for instance, two different compilers for the same
language could be defined — amake points out the conflict, and the user will have to provide
additional information, enabling amake to find a unique solution. The syntax of cluster
definitions, as well as their semantics will be presented at the end of this section, but first a
non-trivial example.
Amoeba 5.3 87
Example
To show a bit more of amake ’s capabilities, consider the extended software configuration of
figure 6.6.
1 %include std-amake.amk;
2
3 SRC = { parse.y, scan.l, comp.c, defs.h, comp.a };
4 FLAGS = ’-DAMOEBA’;
5
6 %cluster # made by default.
7 {
8 %targets comp[type = program];
9 %sources $SRC;
10 %use cc-c(flags => $FLAGS, optimize => %true);
11 };
12
13 %cluster # only made when asked for.
14 {
15 %targets comp debug[type = program];
16 %sources $SRC + debug.c;# extra source
17 %use cc-c(flags => $FLAGS + ’-DDEBUG’);
18 };
88 Amoeba 5.3
Syntax
The syntax of a cluster definition is shown in figure 6.7.
Note that the cluster name may be an expression — rather than just a literal — which has to
evaluate to a string. This makes it possible to include a cluster definition in a generic , which
is handy in case a lot of similarly looking clusters have to be defined. We will give an
example of this construct when generics are defined.
Cluster names can be given as argument to amake , in order to specify another cluster than the
default one (see the section on defaults). A cluster name may also be absent, in which case
amake takes the cluster to be a candidate for use as subcluster. Subclusters create targets that
are sources of other clusters. Given the clusters to be updated, the subclusters are recursively
determined.
The need for this construct arises when either a tool hierarchy creating the targets from the
sources cannot be constructed or — a much more common case — when the user wants to
create a set of targets, each defined by its own cluster containing its private sources, without
having to specify them all on the command line.
The %targets and %sources specification each require an expression delivering a list of
objects. Note that these expressions also may contain attribute assignments. In this case they
are taken to be local attributes, i.e., it is possible in amake to let an object have type C-src
in one cluster, and type text in another.
The %use clause serves two purposes. Firstly, it may be used to specify different defaults
than supplied by the tool definition itself. Secondly, it can be used to remove a conflict that
arises when amake finds that there is no unique set of tools that create the targets from the
sources. Normally, when the standard tool set is used, this should not be necessary.
The %do clause gives an opportunity to override amake ’s default behavior of deciding which
Amoeba 5.3 89
tools to run. It resembles make’s explicit rule mechanism. The statement-list is just a
boolean expression to be evaluated; the semicolons are interpreted as %and operators. This
statement list will generally consist of an exec call, which is executed each time the cluster
is run. Alternatively an explicit tool invocation can be made. In this case it is only executed
when the inputs of the tool (usually consisting of all the sources of the cluster) have changed.
If the statement list does not evaluate to %true, the build of the cluster is said to have failed,
which will be reported.
Clusters that have neither a %use nor a %do clause, are handled as %use clause clusters that
do not change defaults, and thus let amake find out which tools it has to run in what order.
Default Clusters
As amake allows multiple clusters to be declared, there has to be a way to tell it which
clusters are to be brought up to date. Apart from specifying clusters to be built as argument
when amake is invoked, it is also possible to mark some of the clusters as ‘‘default’’. They
will be constructed in case no cluster is mentioned. Default clusters are specified with the
following syntax:
default-def ::= %default expression
The expression given as argument should evaluate to the names of the clusters (or
corresponding targets) to be built by default. Multiple default definition are allowed.
90 Amoeba 5.3
%derive f[type = C-src] %when matches($f, ’%.c’);
%derive f[type = C-incl] %when matches($f, ’%.h’);
%derive f[C-src-or-incl] %when $f ? type == C-src
%or $f ? type == C-incl;
%derive f[any-src] %when %true;
%unknown, by default.
Example
We will first give an example before turning to the general case. Figure 6.9 contains a tool
definition of the UNIX program ‘‘lex’’, which produces a lexical scanner (in C) from a
description file.
As can be seen from this example, tool definitions have a lot in common with functions or
procedures found in common imperative programming languages. Lines 1-7 form the tool
header, giving the name of the tool and (in the parameter list) describing the kind of objects
used as in- and outputs, side-effects and options of the tool. Lines 8-11 contain the tool body,
describing the actions to be executed. In this case, the result of the tool invocation is the
boolean result of the evaluation of the tool body.
Going through the header, we notice that the parameter src is the only parameter which
Amoeba 5.3 91
1 %tool lex (
2 src: %in [type = lex-src];
3 dest: %out [type = C-src] => match($src);
4 temp: %tmp => lex.yy.c;
5 flags: %string %list => {};
6 prog: %in [type = command] => /usr/bin/lex;
7 )
8 {
9 exec($prog, args => $flags + $src);
10 move($temp, $dest);
11 }
does not have a default. This should not be too surprising, because this parameter represents
a possible lex input file — note it is marked %in — and it is amake ’s very job to find out
whether this tool can be applied to one of the files given as source in a cluster. Many tool
definitions (e.g., the ones currently present in the standard tool definition set) have the same
basic structure as the one presented here.
The next parameter (dest on line 3) is of type %out, i.e., it denotes a file which is supposed
to be generated by the command block of the tool. The default name for this file is computed
by the built-in match function. Actually, this function needs some help provided by a
derivation rule, but this will be described in a later section. The result, however, is that an
input file lexer.l will cause an output file lexer.c to be created.
Line 4 makes the hard-wired temporary file ‘‘lex.yy.c’’ explicit. As amake has a locking
mechanism, which takes care of tool synchronization, this will prevent it to run 2 invocations
of Lex at the same time.
The various options of the tool are represented on line 5 by the parameter flags of type
%string %list. As the command Lex only has few options an alternative — perhaps
more convenient, or at least more portable — approach would be to introduce a parameter of
type %boolean, one for each option. The reason why this is not done here, is probably
lazyness on part of the amake programmer.
The syntax of amake ’s tool definitions is presented in figure 6.10.
A tool returns the value resulting from the evaluation of its body, unless a %return
directive is used as file argument. In that case the result consists of the contents of this file
(which can be referred to in the tool body by the parameter name) after the body has been
executed.
92 Amoeba 5.3
tool-def ::= tool-header trigger? tool-body
tool-header ::= %tool identifier "(" [ param-decl ";" ]* ")"
Computed Inputs
Amake contains a very useful feature, allowing tools to make known the actual inputs it has
read during a certain invocation. This can be specified with the %computed clause. Figure
6.11 contains an example of this construct. It is an adaptation of the C-compiler tool
definition currently used in the construction of Amoeba libraries and utilities. For a source
file f.c, the parameter ‘‘deps’’ will expand to f.dep. The -d option requests the C-
compiler to report (on file f.dep) the header files encountered. This list of implicit inputs
will, as soon as the tool body has been completed, be assigned to parameter ‘‘incl’’.
When a tool, making use of this feature, is invoked for the first time, amake is pessimistic (or
at least not overly optimistic) in that it lets the tool wait until all the objects that could
Amoeba 5.3 93
%tool cc-c (
src: %in [type = C-src];
obj: %out [type = loadable] => match($src);
flags: %string %list => $CFLAGS;
prog: %in [type = command] => $CC;
deps: %out [type = dependents]=> match($src);
incl: %in %list [C-incl,implicit] => %computed $deps;
)
{
exec($prog, args=> ’-d’$deps + ’-c’ + $flags + $src);
};
possibly be inputs are available. This is important because some of them might be generated
by other tools. For example, the tool Yacc may produce a header file included by several C
source files.
If the same invocation is to be checked in a later amake run, amake knows both the specified
and the computed input objects, and the tool only has to be rerun when one of these has
changed. As each tool invocation reports the objects that were read, amake will never
erroneously fail to recompile a source file, in the event that some of the header files have
changed.
6.3.10. Expressions
Expressions in amake are typed. The types available are boolean, string, object, unknown
and lists of these. Arguments of operators, functions and tools are automatically converted to
the type required, if possible.
Overview
Objects are amake ’s internal representation of (not necessarily existing) files. Properties of
the files that are of interest to amake are represented as attributes. We must stress that the
correspondence is not very strict: the ‘‘physical’’ object may have certain attributes which
are not represented internally (e.g., contents, although it is possible to represent it), while the
internal object will have attributes (such as type, and other properties used in amake ’s
algorithms) which are not necessarily represented anywhere on the file system. The
hierarchical structure, which most file systems have, is represented internally, however.
As in UNIX and Amoeba, objects are identified with their path name. It is important to note
that amake (in contrast to, e.g., the Bourne Shell and make ) interprets the character ‘‘/’’ as a
real selection operator, rather than just a character which is part of a path name. This has the
advantage that an amake specification file might also be used, without change, in an OS
environment that uses a different way to represent the file hierarchy.
In amake , strings and booleans are used mainly as attribute value and options to tools or
commands. Boolean expressions can also be used to force control flow within a tool
94 Amoeba 5.3
invocation. A special ‘‘unknown’’ value is given to an attribute whose value is neither set
explicitly, nor derivable.
Literals, strings and variable or parameter references were introduced in the section on
lexical elements. The possible confusion whether the string ‘‘´a´’’ or the object ‘‘a’’ is
meant, disappears when the value is used, for example when it appears as source of a cluster.
The meaning of %true, %false and %unknown should be clear. The next entry
represents both built-in function call and tool invocation. Lists (of objects, strings etc.) can
be built with curly brackets (‘‘{’’ and ‘‘}’’). Parentheses can be used to force a grouping,
differing from the default one.
Attributes can be attached to objects by means of the special ‘‘[’’ operator. The literal in the
attr-spec is the name of the attribute to be set. If the expression specifying the value of the
attribute is absent, the value %true is assumed by default. When evaluated, the attribute
assignment expression puts the attributes on its right hand side on the object(s) on its left
Amoeba 5.3 95
hand side, which also becomes the result of the expression. This makes it possible to let list
building coincide with attribute setting, for example as in
SRC = { fool.c[C-src = %false], real.c } [author = versto];
The operators, their priority and their interpretation are summarized in the table of figure
6.13.
operator
priority
interpretation domain range
%not 8
not
boolean
boolean
/ 7
select object x string object
& 6 concat string x string string
+ 5
append
list x list
list
? 5
get object x attribute type
[ 5 put object x attribute x value object
\ 4
remove
list x list
list
== 3
equal type x type boolean
<> 3 unequal type x type boolean
%and 2
and
boolean x boolean
boolean
%or 1
or boolean x boolean boolean
Two lists are equal if and only if their respective components are equal, i.e., the order and
multiplicity of elements does matter. Lists within lists are flattened, so ‘‘{ a, { b } }’’
evaluates to ‘‘{ a, b }’’. Be careful not to forget the commas separating the components
in a list expression: the expression ‘‘{ bet ray }’’ evaluates to ‘‘{ betray }’’,
because of an invisible concatenation operator between the two strings.
Since, as noted before, the slash is an ordinary operator, white space is allowed (but ignored)
in path expressions. The entries in the grammar specification for expression containing the
slash operator are there so that UNIX path names can be accepted without change: a slash on
its own denotes the root directory, so ‘‘/comp’’ selects ‘‘comp’’ within the root directory.
A trailing slash is ignored.
96 Amoeba 5.3
Type Conversion
Type conversion is done automatically when needed; the conversions possible are
summarized in figure 6.14.
from unknown
string
object boolean
unknown *
’unknown’
string ’unknown’ *
./component ’true’, ’false’
object path name *
boolean ’true’, ’false’
*
In words: types boolean and unknown are converted to and from strings by means of ’true’,
i.e., the keywords without the ‘‘%’’. Conversion from object to strings delivers the path name
of the object. If the object resides in the current directory or one of its subdirectories, a
relative path name is generated, otherwise an absolute path name. Conversion from string to
object is done by assuming that an object within the current directory is meant.
When an operator or function expects a list argument, atomic arguments are automatically
converted to the corresponding ‘‘singleton,’’ so ‘‘{ a, b } + c’’ is converted to ‘‘{ a,
b } + { c }’’, which in turn evaluates to ‘‘{ a, b, c }’’.
Amoeba 5.3 97
function arguments
meaning
exec
(prog, args, stdin, execute a command with possible
stdout, stderr)
redirection
if (condition, then, else) conditional evaluation
echo (strings)
print arguments on amake stdout
defined (variable)
has a variable been assigned to?
exit (integer) exit amake prematurely
move (from, to)
rename a file on the file system
select (list, attr, value)
select objects specified
get (object, attribute) return value of attribute, or %unknown
vpath (name, dirs)
first d in dirs for which d/name exists
lcomp (object)
last component of path
basename (object, pattern) remove prefix and/or suffix
matches (string, pattern)
%true iff strings matches pattern
match (object)
match basenames of objects
The following example illustrates the use of defaults and call syntax:
exec(/bin/cc, args => ’-E’ + $base.c, stdout => $base.e)
output file. Diagnostic files, to be shown on standard output when the command or tool has
finished, are created with %diag. Usually, evaluation of commands and tools delivers a
boolean value, indicating success or failure. Supplying %return as argument causes
redirection to a temporary file, whose contents will be returned as a string list — the file is
supposed to contain readable output — so
exec(/bin/cat, args => hello, stdout => %return)
effectively works as a ‘‘contents’’ function. Note that this is equivalent to the command
`/bin/cat hello`
in the Bourne shell.
98 Amoeba 5.3
The function echo can be used to write a informative message on amake ’s standard output.
It delivers %true as return value. The exit function also prints its arguments, but after
that it forces a premature amake exit. This is useful when, for example, some vital variable
has not been set. Examples:
if ($OS == AMOEBA, ainstall($prog))
if (defined(OPTIMIZE), $OPTIMIZE, ’-O’)
if (%not defined(OS), exit(’$OS not defined’), echo(’O.K.’))
The function get is an alias for the ‘‘?’’ operator, i.e., it returns the value of a certain
attribute of an object, possibly after deriving it.
There is also a function select, which filters precisely those objects from an object list that
have a particular value for some attribute. Note that the attribute referred to might have to be
derived first. For example:
select({parse.y, scan.c}, type, C-src) == {scan.c}
As the example indicates, this function can be handy when some special cluster only needs to
have a subset from all sources as input (e.g., only the ‘‘lintable’’ sources).
Object functions
Sometimes it is not known in advance where a file resides (e.g., its place might change from
time to time). The function vpath (the ‘‘v’’ stands for ‘‘view’’) returns the first occurrence
of a file in a list of directories, having a specified string as last component. Example:
PATH = { /usr/local/bin, /bin, /usr/bin, /usr/ucb/bin };
CC = vpath(cc, $PATH);
It is common practice that system commands create output objects based on the name of the
input(s). To give some way to describe this within a tool definition, and also to allow tools
themselves to implement this kind of behavior, the function basename is provided. It can
be used to strip off a known prefix and/or suffix.
The predicate matches returns a boolean telling whether its first argument matches the
pattern given as second parameter. This function is often used in a derivations of the type
attribute.
The function match (not to be confused with the previous one) involves some special
trickery, and can only be used in tool contexts to derive the name of an output object based
on that of an input object. This will be described in a later section.
Examples:
basename(file.c, ’%.c’) == file
basename(pre.file.post, ’pre.%.post’) == file
basename(file.c, ’%.y’) == file.c
Amoeba 5.3 99
6.3.11. Assignments and Declarations
In amake , variables are primarily used to avoid having to repeat lists of strings or objects in
different places. The variables are not textual macros, so assignments expect an expression
as right hand side.
assignment ::= [ identifier "=" ]? expression
As expressions are typed, so are variables. Note that there are no restrictions on the kind of
expressions: tools may also be called, for instance. Variables may be assigned more than
once.
Assignments are not necessarily executed directly the moment they are read; they might be
cached until their value is used, or all input has been read. If an expression should evaluated
because of its side effect rather than its resulting value, it is allowed just to mention the
expression itself. In that case, the expression is evaluated right away. Example:
%if (%not defined(TOOLSET), {
exit(’panic: TOOLSET not defined’); # exit right away
});
Values of variables can also be taken from the environment and put in the environment of
commands executed. See the section on import and export for details. The following
variables have a special interpretation:
PWD the directory containing current file being read
AMAKELIB the search path for files to be %included
ROOT the root object, ‘‘/’’
The variable PWD is particularly useful: it makes it possible to maintain amake source
descriptions, completely independent of the place where the targets are built. The source
description file, residing in the same directory as the sources, can refer to the sources by
means of ‘‘$PWD/sourcename’’. An Amakefile in the configuration directory can
then include the source lists required, and specify what should be constructed out of it.
Declarations
Declarations can be used to attach attributes to a list of objects.
declaration ::= %declare expression-list
Each expression in the expression list is required to be an attribute assignment. The
expressions are evaluated one by one, which has the effect of the attributes being set.
Example:
%declare $SRC[author = versto], foo.c[author = nobody];
It should be noted that the same effect could be achieved by means of assignments, but the
advantage of using declarations is that they do not require invention of a variable name that is
not used afterwards.
A possible application is the situation where a derivation rule is used to set the value of some
attribute by default, and a declaration takes care of setting the attributes to some different
value for the exceptions. So a more generic version of the example above would be:
6.3.13. Generics
Generics can be used to avoid having to repeat definitions that are equal up to some values
(strings, objects, booleans). Generics have to be defined in a %generic clause, before they
can be instantiated with an %instance directive, which contains the generic’s name and
the values for its parameters.
generic ::= %generic identifier "(" id-list ")" "{" amake-def-list "}"
id-list ::= identifier [ "," identifier ]*
instance ::= %instance identifier "(" argument-list ")"
In the current implementation, instantiation of a generic has the effect of making (global)
assignments to the parameters, followed by a copy of the body of the generic. This means
that care must be taken not to let a generic’s parameters coincide with one of other variables.
We will give two examples where the use of generics makes the specification simpler, and
less error prone. The first deals with derivations, and is used in many tool libraries. The
definition, which is part of the standard tool set, is shown in figure 6.17.
A possible instantiation (in this case for the cc-c tool) would be:
%instance deftypesuffix(C-src, ’%.c’);
%instance deftypesuffix(C-incl, ’%.h’);
%instance deftypesuffix(loadable, ’%.o’);
An instantiated version of deftypesuffix takes care of linking the type and the def-pat (short
for ‘‘default-pattern’’) attribute of an object. The first derivation of the generic (line 2) is
used to derive the type of an object when its name matches some pattern (e.g., an object with
name matching ‘‘%.c’’ is considered to have ‘‘type = C-src’’ by default). The other
derivation included in the generic (line 3) has to do with the rather special semantics of the
match function. This function is used to specify names of generated objects, having the
basename with the input in common. To be able to do this, it requires
the def-pat attribute of the generated object, supplying prefix and/or suffix of the name
of the generated objects.
the base attribute of the source object, which replaces the ‘‘%’’ in the pattern for the
generated object.
The irony — or perhaps confusing detail — is, that the base attribute of the source object is
also derived, using the def-pat attribute (see lines 6 and 7).
The second example deals with clusters. Consider the case when we have a whole bunch of
programs to maintain, all using tools with the same defaults. Of course we could introduce a
cluster for each configuration and copy the %use clauses. This has the problem that we have
to change each cluster when we would like to override one tool default. A better way to do
this is therefore first to create a generic as is done in figure 6.18.
In the instantiations we then only need to specify target and sources, for each of the
configurations to be maintained. Note that in the definition of gencluster a %default
clause is included in order to let all instances created with this definition to be updated by
default.
Of course this generic can also be extended to have a number of tool parameters as argument,
to use conditionals (see below), or to define several other clusters — such as print, install ,
lint, etc. — as well. For all not too complicated configurations, an amake specification along
the lines of the example in figure 6.19 would then be sufficient.
6.3.14. Conditional
There is also a top-level conditional available, comparable to the ‘‘#if’’-construct in the C-
preprocessor. It has the almost same syntax as the conditional expression (although it is
debatable if that is really an advantage):
conditional ::= %if "(" expression "," def-list ["," def-list]? ")"
def-list ::= "{" amake-def-list "}"
The condition may be an arbitrary boolean expression. Note that, in contrast to conditional
expressions, the ‘‘else’’ part is optional. This is equivalent to defining the else part as being
empty. Also note that the ‘‘then’’ and ‘‘else’’ part are arbitrary sequences of amake
definitions, so nested conditionals are allowed.
A possible application is to assign default values for variables used in tool definitions. As the
variables might well be defined before the tool itself is included, the default assignment in
the tool library has to be shielded off. Examples:
%if (%not defined(CFLAGS), {
CFLAGS = { ’-O’ };
});
Note that the variable CFLAGS has been export with the space as separator. Otherwise it
would have resulted in the value ‘‘-DAMOEBA:-DACK’’.
6.4.1. Patterns
A <pattern> may consist of any valid C expression. If the <pattern> consists of two
expressions separated by a comma, it is taken to be a range and the <action> is performed
on all lines of input that match the range. A <pattern> may contain ‘‘regular
expressions’’ delimited by an @ symbol. Regular expressions can be thought of as a
generalized ‘‘wildcard’’ string matching mechanism, similar to that used by many operating
systems to specify file names. Regular expressions may contain any of the following
characters:
x An ordinary character.
\ The backslash quotes any character.
ˆ A circumflex at the beginning of an expr matches the beginning of a
line.
$ A dollar-sign at the end of an expression matches the end of a line.
:x A colon matches a class of characters described by the next character.
:a ‘‘:a’’ matches any alphabetic.
:d ‘‘:d’’ matches digits.
:n ‘‘:n’’ matches alphanumerics.
6.4.2. Actions
Actions are expressed as a subset of the C language. All variables are global and default to
int’s if not formally declared. Only char’s and int’s and pointers and arrays of char and int
are allowed. Bawk allows only decimal integer constants to be used—no hex (0xnn) or octal
(0nn). String and character constants may contain all of the special C escapes (\n, \r, etc.).
Bawk supports the ‘‘if’’, ‘‘else’’, ‘‘while’’ and ‘‘break’’ flow of control constructs, which
behave exactly as in C.
Also supported are the following unary and binary operators, listed in order from highest to
lowest precedence:
Comments are introduced by a ’#’ symbol and are terminated by the first newline character.
The standard ‘‘/*’’ and ‘‘*/’’ comment delimiters are not supported and will result in a
syntax error.
Fields may also be modified with the strcpy function (see below). For example, the
expression:
strcpy( $4, "Addr." );
applied to the same line above would yield:
120 PRINT "Name Addr. Zip"
6.4.5. Limitations
The maximum input line is 128 characters. The maximum action is 4K.
6.5.1. Monitoring
It is possible to monitor various ‘‘events’’ in a server program. This includes Amoeba
kernels since they have internal servers. This is currently only supported in the C
programming language. There is a special include file, called monitor.h , which implements
the mechanism for storing and retrieving the monitor information. A special macro can be
placed in the code which acts as an ‘‘event marker’’. The macro keeps a count of the number
of times it has been executed. The statistics kept by the monitoring can be collected using the
command monitor (U). This command allows the possibility of keeping a circular buffer of
events in the order in which they occur in addition to the count of the number of times an
event has occurred. This option tends to use a lot of memory and shouldn’t be left on too
long in important servers.
The macro that implements event marking is called MON EVENT. The syntax of the call is:
MON EVENT("description of an event")
Execution of this call generates a primitive event. A string describing the event is passed as
an argument. A variable denoting the number of times this specific event has occurred is
incremented. Memory within the server is used to buffer the event information.
The three RPC calls are automatically monitored. There are events for each call to trans,
getreq and putrep. In addition special events are kept for any RPC errors.
The server does not need to implement a special command to allow clients to request the
monitoring statistics. The include file redefines getreq call to implement the monitor
command without the server seeing it.
At start up time the monitoring system allocates memory to store the event information using
malloc (L). Some times this is impossible or undesirable since it may modify the behavior of
the system being monitored in unacceptable ways. It is possible to pass the monitoring an
event buffer using the MON SETBUF macro. The syntax of the call is as follows.
MON SETBUF(buffer, size)
A pointer to the private buffer and its size should be passed as arguments.
Note that it is not a good idea to add MON EVENT calls to timing critical code. Although
inexpensive there is some overhead involved.
Name
ansi C − details of STD C library conformance
Synopsis
Support for STD C library functions and header files is discussed.
Description
Several STD C conformant compilers have been ported to Amoeba. The principle compilers
are the ACK and the GNU compilers. Details of the ACK compiler conformance are
provided in the languages section of the Programming Guide .
Amoeba provides its own C library rather than using those of a specific compiler. This
library is the subject of the rest of this manual entry.
The various STD C functions are found in either libamoeba.a , libajax.a and libmath.a .
Specific deviations from the C Standard are listed below where applicable. Many header
files define additional names that are disallowed by STD C. The library also contains many
reserved names that should not be overridden by applications (e.g., trans). STD C requires
that only the names defined by STD C are reserved, but this requires that every Amoeba
function name has a leading underscore. This has not been fully implemented yet, so be
careful.
All supported header files are found in the include subdirectory posix. Since the POSIX
standard defines extensions to many STD C headers, separating STD C headers from POSIX
headers would be impossible.
Only functions and header files with peculiarities are mentioned. The rest are adequately
described by the ISO C standard.
<errno.h>
Currently declares the integer variable errno, the constants EDOM and ERANGE and many
constants required by POSIX. Warning: use of errno by concurrent threads is not safe; to fix
this, it will eventually change into a macro that evaluates to a modifiable lvalue. For future
compatibility, applications should not write
extern int errno;
but rather include errno.h.
<limits.h>
Defines all the constants required by STD C, as well as some required by POSIX.
<setjmp.h>
Apart from the standard requirements, this header file also defines the type sigjmp buf and
the functions sigsetjmp() and siglongjmp() required by POSIX. Note that part of these
definitions are machine-dependent and are placed in the file
posix/machdep/architecture/ setjmp.h . The directory where this file resides should be passed
as a separate −I flag to the C compiler.
<stdio.h>
Apart from the standard requirements, the POSIX function fdopen() and function/macro
fileno() are supported.
<string.h>
Warning: the function strtok() uses a static global variable; its use by concurrent threads is
not safe.
<time.h>
The type time t is in fact an integral type and measures seconds since January 1, 1970
(defined more precisely by POSIX). Additional information about these functions is found in
ctime (L). Warning: many of these functions use global variables; their use by concurrent
threads is not safe. In particular, asctime(), ctime(), gmtime(), localtime() return pointers to
static storage that may be overwritten by other threads; most also share a hidden cache of
timezone information that may be corrupted by concurrent accesses.
See Also
posix(L).
Synopsis
#include "ampolicy.h"
Description
This include file contains system parameterization information which may be modified by
the system administrator on a system-wide basis. In particular it contains path names for
capabilities and data for certain servers and implies a lot about the structure of the file
system. It comes set up to work with the directory structure defined in the command
newsoapgraph(A).
Administration
Before compiling the system it is necessary to make all the desired alterations to this file. It
determines to a large extent the directory structure required by the various programs. Make
sure that any changes made to this file that fundamentally alter the structure of the directory
graph are reflected in the shell scripts for building the directory graph, namely
newsoapgraph(A) and newuser (A).
See Also
newsoapgraph(A), newuser(A).
Name
ar − ASCII representations of common Amoeba types
Synopsis
#include "amoeba.h"
#include "module/ar.h"
char * ar cap(cap p)
char * ar port(port p)
char * ar priv(priv p)
Description
To aid in debugging a set of routines has been provided to produce human-readable
representations of capabilities and various subparts thereof.
The first three functions return a pointer to a string containing the ASCII representation of
the bytes in a capability, port and private (see rpc(L)), respectively. Note that the string is
in static data and subsequent calls by other threads or the same thread will overwrite the
contents of the string. Each routine has its own static data so there are no interactions
between the individual routines.
The latter three functions convert a string of the format returned by the first three routines to
a capability, port and private, respectively. The ar to functions assign to the capability, port
or private, pointed to by the second parameter. They return a pointer to the character beyond
the last character of the string. If the string is supposed to contain no extra characters, you
should check that the returned pointer points to a NUL character.
If the string passed to an ar to function is illegal, NULL is returned.
The format used is explained below.
Functions
ar port
char *
ar port(port p)
port *port p;
Ar port returns a pointer to a string with the six bytes in the port represented as x:x:x:x:x:x ,
where x is the value of a byte in hexadecimal.
ar cap
char *
ar cap(cap p)
capability *cap p;
The format used by ar cap is the concatenation of the formats of ar port and ar priv
respectively, separated by a slash. That is, x:x:x:x:x:x/D(X)/x:x:x:x:x:x .
ar tocap
char *
ar tocap(s, cap p)
char *s;
capability *cap p;
Ar tocap takes a string s in the format produced by ar cap and makes a capability that
matches that representation in the capability pointed to by cap p. It returns a pointer to the
first character after the parsing of s stopped.
ar toport
char *
ar toport(s, port p)
char *s;
port *port p;
Ar toport takes a string s in the format produced by ar port and makes a port that matches
that representation in the capability pointed to by port p. It returns a pointer to the first
character after the parsing of s stopped.
ar topriv
char *
ar topriv(s, priv p)
char *s;
private *priv p;
Ar topriv takes a string s in the format produced by ar priv and makes a private part of a
capability (i.e., object number, rights and check field) that matches that representation in the
capability pointed to by priv p. It returns a pointer to the first character after the parsing of s
Examples
The following code prints out an array of capabilities:
#include "amoeba.h"
#include "module/ar.n"
printcaps(caps, n)
capability caps[];
int n;
{
int i;
The following function prints ‘‘hello world’’ and stores the port consisting of the bytes 1, 2,
3, 4, 5, 6 in p.
Hello()
{
port p;
See Also
c2a(U), rpc(L).
Name
bprintf − like sprintf but with bounds checking
Synopsis
#include "module/strmisc.h"
Description
Bprintf is a function similar to sprintf in the STD C stdio library. It takes the format string
fmt and copies it to the buffer specified by begin and end, doing any conversions of extra
arguments according to the rules of printf . However it does bounds checking so that it does
not overrun the end of the buffer. It returns a pointer to the next free position in the buffer.
This is useful for doing multiple bprintf ’s to the same buffer. If it overruns the buffer it
returns the NULL-pointer. If begin is the NULL-pointer and end is non-NULL then it will
immediately return the NULL-pointer without modifying the buffer. This means that one can
put many bprintf requests after another for the same buffer, using the result of the previous
bprintf as the begin pointer for the next and never need to check for an error until after the
last one. For example,
#define BSZ 30
char buf[BSZ];
char * end = buf + BSZ;
char * p:
int i = 20;
p = bprintf(buf, end, "foo %d", i);
p = bprintf(p, end, " test");
p = bprintf(p, end, "\n");
if (p == (char *) 0)
ERROR("buffer overflow");
/* else all is well ... */
If the end pointer is the NULL-pointer then bprintf functions as though it was printf . Note
that bprintf will NULL-terminate the resultant string only if it has not yet reached the end of
the buffer. Thus, if the buffer is too small to hold all the data then it will not be NULL-
terminated.
Name
buffer − getting and putting data in architecture-independent format
Synopsis
#include "amoeba.h"
#include "module/buffers.h"
Description
These functions get data from, or put data into, a character buffer in a format that is
independent of any machine architecture; in particular of byte-order dependencies. They are
primarily useful in loading and unloading RPC buffers (and reply buffers). All of them have
the same calling sequence and return value, except for the type of the last argument.
In each case, p should point to the place within the buffer where data is to be stored or
retrieved, and endp should point to the end of the buffer. (That is, the first byte after the
buffer.)
For the buf get functions, the val argument should be the address where the data retrieved
from the buffer is to be stored. It must be a pointer to the type of value expected by the
function. (See the individual function descriptions, below.)
For the buf put functions, there are two possibilities: if the value to be put in the buffer is an
integral type, or a character pointer, the value itself should be passed as the val argument.
For larger, structured types, a pointer to the value should be supplied. (See the individual
Functions
char *
buf get capset(p, endp, valp)
char *p, *endp;
capset *valp;
A capability-set is retrieved from the buffer pointed to by p and copied to the capability-set
pointed to by valp. The suite for the capability-set is allocated by this function. The suite
must not contain more than MAXCAPSET entries.
buf get pd
char *
buf get pd(p, endp, valp)
char *p, *endp;
process d *valp;
A process descriptor stored in an architecture-independent format is retrieved from the buffer
pointed to by p and stored in the process descriptor structure pointed to by valp. All
necessary allocation of sub-structures is performed by this function. See process d(L) for
more details.
char *
buf put capset(p, endp, valp)
char *p, *endp;
capset *valp;
The capability-set pointed to by valp (including the suite sub-structure) is copied into the
buffer pointed to by p. The suite must not contain more than MAXCAPSET entries.
capability c, c2;
int32 i, i2;
char *s, *s2;
char buffer[1000];
char *p, *ep;
p = buffer;
ep = buffer + sizeof buffer;
p = buf put cap(p, ep, &c);
p = buf put int32(p, ep, i);
p = buf put string(p, ep, s);
if (!p) {
error("Buffer overflow during put");
}
p = buffer;
ep = buffer + sizeof buffer;
p = buf get cap(p, ep, &c2);
p = buf get int32(p, ep, &i2);
p = buf get string(p, ep, &s2);
if (!p) {
error("Buffer underflow during get");
}
Name
buildstack − build a stack segment for a process to be executed
Synopsis
#include "amoeba.h"
#include "module/proc.h"
Description
Buildstack is a low-level utility function used by exec file(L). It fills the buffer with a stack
segment for a process to be executed. (‘Stack’ segment is a slight misnomer − the
information filled in is better described as environment, but it happens to be placed above the
stack in memory, and the rest of the segment is used as the stack for the main thread.)
Buildstack places the arguments, the string and capability environments and pointers to them
in the buffer, starting from the high end, all as expected by the C run-time start-off, head(L).
The argument start indicates the address where buf will be mapped in the new process’s
virtual memory. See head(L) for a description of the lay-out.
The return value is the stack pointer to be used when starting the process.
Diagnostics
A NULL-pointer is returned if the buffer was too short.
See Also
exec file(L), head(L).
Name
bullet − the Bullet Server client interface stubs
Synopsis
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
#include "server/bullet/bullet.h"
Description
Along with the standard server stubs (see std(L)) the Bullet Server stubs provide the
programming interface to the Bullet Server. They are divided here according to whether they
are administrative or user functions. Bullet files can be created incrementally but do not exist
for reading until they have been committed. The commit operation is atomic.
Types
The file bullet.h defines the type b fsize which is the size of a bullet file in bytes. It is
implemented as a suitably sized integer.
Access
Access to files and administrative functions is determined on the basis of rights in the
capability given with each command.
There is a special capability known as the super capability which is primarily for use by the
file system administrator. The super capability is used for checking the file system,
enquiring the status of the file system and for initiating garbage collection. It cannot be used
to alter or destroy files except for those found to be defective by the file system consistency
checker and those which are garbage collected due to lack of use.
The following rights are defined in Bullet Server capabilities:
Errors
All functions return the error status of the operation. The user interface to the Bullet Server
returns only standard error codes (as defined in stderr.h ). All the stubs involve transactions
and so in addition to the errors described below, all stubs may return any of the standard RPC
error codes (see rpc(L)). A valid capability is required for all operations and so all
operations will return STD CAPBAD if an invalid capability is used. For operations
requiring rights, if insufficient rights are present in the capability then STD DENIED will be
returned. If illegal parameters such as NULL-pointers or negative buffer sizes are given,
exceptions may occur rather than an error status being returned. The behavior is not well
defined under these conditions.
Administrative Functions
b disk compact
errstat
b disk compact(supercap)
capability *supercap;
B disk compact goes through the entire virtual disk of the Bullet Server and moves all the
files down so that there is no disk fragmentation. This is a very slow process and should only
be attempted when there is no load on the system. The Bullet Server does respond to other
requests while it is doing this but very slowly. The capability given as the parameter must
have all rights for the command to succeed.
Required Rights:
BS RGT ALL in supercap
Error Conditions:
STD NOTNOW: the server is already doing a compaction.
b sync
errstat
b sync(supercap)
capability *supercap;
B sync ensures that all committed files are written to disk. The function returns a failure
status only if the capability is not the super capability for the server, if something goes wrong
with the RPC or if there is a disk error. Otherwise it returns STD OK.
Required Rights:
NONE
User Functions
b size
errstat
b size(filecap, size)
capability *filecap;
b fsize *size;
If there are no errors, b size returns in size the number of bytes in the file specified by
filecap. Otherwise size is unaltered.
Required Rights:
BS RGT READ in filecap
Error Conditions:
STD ARGBAD: offset > size of file
offset < 0
to read < 0
STD NOMEM: buffer cache was full
In the remaining functions the type of commit is implemented as an int, but it contains two
flags:
BS COMMIT: If set the file should be committed.
BS SAFETY: If set then wait till the file is written to disk.
Otherwise return immediately.
If the BS COMMIT flag is set in commit then the current contents of the file is the entire file
and the file is created. If the BS SAFETY flag is also set in commit then the function will
wait until the file is written to disk before returning. Otherwise it will return immediately.
(Note that BS SAFETY is ignored if BS COMMIT is not set.) If the BS COMMIT flag is not
set further alterations to the file are expected using either b modify, b delete or b insert (see
below). To avoid system abuse, if the BS COMMIT flag is not set and if no further
modification command is received within BS TIMEOUT sweeps after the previous
modification then the capability will be invalidated. (See bullet (A) for an explanation of
sweeping.)
Note that when calculating file offsets the first byte in a file is numbered 0.
Error Conditions:
STD NOSPACE: resource shortage: inode, memory, disk space
b modify
errstat
b modify(filecap, offset, buf, size, commit, newcap)
capability *filecap;
b fsize offset;
char *buf;
b fsize size;
int commit;
capability *newcap;
If the file specified by filecap has been committed, b modify makes an uncommitted copy of
it. If the file was uncommitted it is used directly. The file is overwritten with the size bytes
pointed to by buf, beginning at offset bytes from the beginning of the file. If offset + size is
greater than the file size then the file will become larger. Note that offset cannot be larger
than the current file size.
To commit a file that is already in the Bullet Server without adding anything further to it,
simply do a b modify with size 0. If the BS COMMIT flag is not set it will restart the
timeout. The capability for the new file is returned in newcap.
Note Well: This operation is not atomic unless the amount of data sent to the file server was
less than or equal to BS REQBUFSZ (as defined in bullet.h ). If more data than this was to be
Error Conditions:
STD ARGBAD: offset < 0
offset > file size
size < 0
STD NOSPACE: resource shortage: inode, memory, disk space
b insert
errstat
b insert(filecap, offset, buf, size, commit, newcap)
capability *filecap;
b fsize offset;
char *buf;
b fsize size;
int commit;
capability *newcap;
If the file specified by filecap has been committed, b insert makes an uncommitted copy of
it. If the file was uncommitted it is used directly. B insert inserts the size bytes in buf
immediately before the position offset bytes from the beginning of the file. The file size will
increase by size bytes.
The capability for the file is returned in newcap.
Note Well: This operation is not atomic unless the amount of data sent to the file server was
less than or equal to BS REQBUFSZ (as defined in bullet.h ). If more data than this was to be
sent and a failure status is returned it is possible that some part of the operation succeeded.
The resultant state of the file is not able to be determined.
Required Rights:
BS RGT MODIFY and BS RGT READ in filecap
Error Conditions:
STD ARGBAD: size <= 0
offset < 0
offset > file size
STD NOSPACE: resource shortage: inode, memory, disk space
Error Conditions:
STD ARGBAD: size <= 0
offset < 0
offset > file size
STD NOSPACE: resource shortage: inode, memory, disk space
See Also
bfsck(A), bstatus(A), bsync(A), bullet(A), fromb(U), rpc(L), std(L), std age(A),
std copy(U), std destroy(U), std info(U), std restrict(U), std status(U), std touch(U),
tob(U).
Name
capcache − capability cache management routines
Synopsis
#include <module/cap.h>
Description
The capability cache routines are used to manage a least recently used (LRU) cache of known
capabilities. The cache is dynamically allocated by the routine cc init which takes as its
parameter the number of entries the cache should have. Entries are looked up in the cache
using the routine cc restrict . If an entry is not found in the cache it is added. The idea is that
instead of repeatedly performing STD RESTRICT commands (see std(L)), the restricted
versions of a capability are kept in the cache. The cache is protected from concurrent updates
using mutexes (see mutex (L)).
The routine cap cmp simply determines if two capabilities are identical.
cc init
int
cc init(n entries)
int n entries;
This routine allocates sufficient memory for a capability cache with n entries slots and
initializes the cache to empty. It is only possible to have one capability cache per process. It
returns 0 if the memory allocation failed. It returns 1 if the cache was successfully allocated
or if a cache has already been allocated (even if it is a different size from the one requested!).
cc restrict
int
cc restrict(cap, mask, new, restrict)
capability *cap;
rights bits mask;
capability *new;
int (*restrict)();
This routine attempts to look up the capability cap in the capability cache and returns in new
the same capability but with the rights restricted to those set in mask .
cap cmp
int
cap cmp(cap1, cap2)
capability *cap1, *cap2;
This routine compares the capabilities pointed to by cap1 and cap2. If they are identical in
all respects it returns 1. Otherwise it returns 0.
See Also
capset(L).
Name
capset − routines for manipulating capability-sets
Synopsis
#include "amoeba.h"
#include "capset.h"
Description
Capability-sets are used by the Soap Server to hold several capabilities for a single name.
This permits the multiple capabilities for a replicated object to be stored under a single name.
Types
The type capset is defined in capset.h . The definition of a capset is as follows.
typedef struct {
short cs initial;
short cs final;
suite *cs suite;
} capset;
and a cs suite is a pointer to an array of the following structure.
typedef struct {
capability s object;
short s current;
} suite;
The suite array may have been allocated with malloc (L) so it is important to use the routines
provided to manipulate them so as to avoid memory leaks. The structure member s current
in the suite tells whether the capability in s object is valid. Invalid capabilities are not
thrown out of the suite , but simply marked invalid so that their slot may be reused later. This
saves lots of fooling around with malloc ed data.
The structure member cs final in the capset holds the size of the array of suite structs. The
structure member cs initial must be greater than or equal to 0 and less than or equal to
cs final. It is not otherwise used and is obsolete.
Cs copy copies the capability-set cs to the capability-set new. Any suites needed are
allocated with calloc (see malloc (L)) and added to the structure. Note that *new is not
allocated, but must already exist. It returns 1 if the copy was successful and zero if it failed
(due to the failure of calloc ).
cs free
void
cs free(cs)
capset *cs;
Cs free frees any memory allocated for suites and marks cs as containing no capabilities. It
is not an error to call this routine for a capset that has already been freed.
cs member
int
cs member(cs, cap)
capset *cs;
capability *cap;
Cs member returns 1 if the capability cap is a member of the capability-set cs. Otherwise it
returns 0.
cs purge
errstat
cs purge(cs)
capset *cs;
cs purge attempts to destroy all the objects whose capabilities are in the capability-set cs. If
the destroy command for a capability succeeds it is marked as no longer being a valid
member of the capability-set. If any of the destroy operations fail then it returns the error
status of the first failure but continues to try to destroy the other objects. If the value of the
function is not STD OK the only way to see what was not destroyed is to look at cs and see
which capabilities are still valid.
cs goodcap
errstat
cs goodcap(cs, cap)
capset *cs;
capability *cap;
Cs goodcap attempts to get a usable capability from a capset, copying it to *cap. If the
capset is empty, it returns STD SYSERR. If there are no caps for which std info returns
STD OK, it copies the last capability in the set and returns the error status from std info with
it. The goodport(L) module is used to avoid attempting a transaction with a port that has
previously returned RPC NOTFOUND.
cs to cap
errstat
cs to cap(cs, cap)
capset *cs;
capability *cap;
Cs to cap gets a capability from the capset pointed to by cs and copies it to the capability
pointed to by cap. It is functionally the same as cs goodcap, above, except that when the
capset contains only one capability, it copies that capability without checking whether it is
good. Also, it always returns STD OK, unless the capset is empty.
See Also
buffer(L), capcache(L), goodport(L).
Name
circbuf − circular buffer - serialized, buffered data transfer between concurrent threads
Synopsis
#include "amoeba.h"
#include "semaphore.h"
#include "circbuf.h"
int cb putc(cb, c)
int cb puts(cb, s, n)
int cb getc(cb)
int cb trygetc(cb, maxtimeout)
int cb full(cb)
int cb empty(cb)
void cb setsema(cb, sema)
Description
A circular buffer provides serialized, buffered data transfer between concurrent threads.
Threads within a process can read from a circular buffer, while other threads in the same
process can write to it. The circular buffer module provides a set of routines to manage the
circular buffers.
Data transfer through circular buffers is similar to transferring data through a pipe. Threads
write data into the pipe, other threads read data from the pipe. A close on a circular buffer is
similar to closing the input side of a pipe: it is no longer allowed to put new data into the
circular buffer but the circular buffer can still be emptied.
Data transfer through a circular buffer can be accomplished by the routines cb getc,
cb trygetc and cb gets to read data, and cb putc and cb puts to write data. Each function
copies data respectively from the circular buffer to a local buffer or from a local buffer to the
circular buffer.
Functions
cb alloc
struct circbuf *
cb alloc(size)
int size;
Cb alloc allocates a circular buffer of size bytes and returns a circular buffer reference
pointer. If the memory could not be allocated it returns the NULL-pointer.
cb free
void
cb free(cb)
struct circbuf *cb;
Cb free destroys the circular buffer. All data in the circular buffer is lost. Cb free may not
be called while threads are still using the circular buffer; it will cause threads to hang or
crash.
cb putc, cb puts
int
cb putc(cb, c)
struct circbuf *cb;
int c;
int cb puts(cb, s, n)
struct circbuf *cb;
char *s;
int n;
Cb putc and cb puts write data to the circular buffer. Cb putc writes the character c to the
circular buffer. Cb puts writes n characters from the character array s to the circular buffer.
Cb puts is not an atomic operation (except when writing one character). Two concurrent
cb puts calls to the same circular buffer might result in mixed data. Use cb putp and
cb putpdone for atomic writes to circular buffers. It is possible, however, to make sure that
cb puts is atomic: if all accesses to the buffer are through cb puts and cb gets, all these
accesses put or get objects of the same size and the size of the circular buffer is a multiple of
this size.
Cb putc returns −1 when the circular buffer is closed, zero upon successful writes. Cb puts
returns 0 on success, −1 when the circular buffer is closed. Note that cb puts will also return
−1 if the buffer is closed during the cb puts call, so some bytes may have been written.
int
cb trygetc(cb, maxtimeout)
struct circbuf *cb;
int maxtimeout;
Cb getc and cb trygetc read a byte from the circular buffer. They both return the character
read. Cb trygetc completes when it has read a character or when no character has become
available after waiting for maxtimeout milliseconds.
Cb getc and cb trygetc return −1 if the circular buffer is closed and there are no input bytes
available. Cb trygetc returns 1 when the timer has expired.
Note: The routine cb trygetc behaves on signals exactly as mu trylock (see signal(L),
mutex (L)). If the thread doing circular buffer actions has defined a signal catcher, the signal
will be caught. Otherwise the signal will be ignored. On a caught signal, cb trygetc will
return a −1 value. When maxtimeout equals −1, cb trygetc will be blocked until a byte
arrives. That is, there is no maximum timeout. In this case, cb trygetc is still interruptible
by a signal.
cb gets
int
cb gets(cb, s, minlen, maxlen)
struct circbuf *cb;
char *s;
int minlen, maxlen;
Cb gets reads at least minlen and at most maxlen bytes from the circular buffer into the
character array s. Cb gets will block when the number of bytes available is less than the
minimum requested (available < minlen). Cb gets returns the number of bytes read.
Cb gets returns zero when the circular buffer is closed.
cb putp, cb putpdone
int
cb putp(cb, ps, blocking)
struct circbuf *cb;
char **ps;
int blocking;
Diagnostics
cb putp returns −1 on closed circular buffers, otherwise it returns the amount of free bytes
available. Cb putpdone returns no status.
Note: A sequence cb putp() − ‘‘store data’’ − cb close() − cb putpdone(), is allowed but the
stored data is flushed.
cb getp, cb getpdone
int
cb getp(cb, ps, blocking)
struct circbuf *cb;
char **ps;
int blocking;
void
cb getpdone(cb, len)
struct circbuf *cb;
int len;
Cb getp and cb getpdone are the complement of cb putp and cb putpdone. Cb getp
returns in *ps a pointer to the available data bytes. The number of available data bytes is
passed back as function value. Data can be copied directly copied from the circular buffer
via *ps.
When blocking is zero, cb getp does not block. It returns the number of data bytes
immediately available (including zero). When blocking is negative, cb getp waits for
available data bytes, but is interruptible by a signal (see cb trygetc ). When signaled and
there are no data bytes, cb getp returns a zero value. When blocking is positive, cb getp
cb full, cb empty
int
cb full(cb)
struct circbuf *cb;
int
cb empty(cb)
struct circbuf *cb;
Cb full returns the number of available data bytes in a circular buffer. Cb empty returns the
number of available free bytes in a circular buffer.
Cb full returns a −1 when there are no available data bytes and the circular buffer is closed.
Cb empty returns −1 when the circular buffer is closed.
cb setsema
void
cb setsema(cb, sema)
struct circbuf *cb;
semaphore *sema;
Cb setsema attaches an external semaphore to a circular buffer. Each time a data byte
arrives, the semaphore sema is upped by sema up (see semaphore(L)). When the circular
buffer is closed, the semaphore is upped as well. The semaphore is initialized to the number
of available data bytes.
Example
The following demonstrates the fundamentals of using the circular buffer routines in a
multi-threaded environment.
main(argc, argv)
char **argv;
{
int num bytes, n;
char s[10];
/* stop thread */
thread exit();
}
See Also
rpc(L), semaphore(L), thread(L).
Name
ctime − convert date and time to ASCII
Synopsis
extern char *tzname[2];
void tzset()
#include <sys/types.h>
char *ctime(clock)
time t *clock;
#include <time.h>
char *asctime(tm)
struct tm *tm;
struct tm *localtime(clock)
long *clock;
struct tm *gmtime(clock)
long *clock;
time t mktime(tm)
struct tm *tm;
Description
Tzset uses the value of the environment variable TZ to set time conversion information used
by localtime. If TZ does not appear in the environment, the best available approximation to
local wall clock time, as specified by the tzfile-format (see zic(A)) file localtime in the
system time conversion information directory, is used by localtime. If TZ appears in the
environment but its value is a NULL-string, Coordinated Universal Time (UTC) is used
(without leap second correction). If TZ appears in the environment and its value is not a
NULL-string:
if the value begins with a colon, it is used as a path name of a file from which to read
the time conversion information;
if the value does not begin with a colon, it is first used as the path name of a file from
FILES
/profile/module/time/zoneinfo timezone information directory
/profile/module/time/zoneinfo/localtime local timezone file
/profile/module/time/zoneinfo/posixrules used with POSIX-style TZ’s
/profile/module/time/zoneinfo/GMT for UTC leap seconds
Warnings
The return values point to static data whose content is overwritten by each call. The
tm zone field of a returned struct tm points to a static array of characters, which will also
be overwritten at the next call (and by calls to tzset ).
Avoid using out-of-range values with mktime when setting up lunch with promptness
sticklers in Riyadh.
Example
The following program prints the current local time:
#include <stdio.h>
#include <time.h>
main()
{
time t t;
time(&t);
printf("%s", ctime(&t));
exit(0);
}
Name
dgwalk − routines for examining a directory graph
Synopsis
#include "amoeba.h"
#include "module/dgwalk.h"
errstat dgwalk(params)
errstat dgwexpand(path, proc)
Description
Dgwalk can be used to find all directory and directory entries that are reachable from one or
more starting points. For each directory found, dgwalk calls a user-supplied function with as
parameters one or more capabilities for the directory and an indication of the path names
under which these capabilities can be found. Dgwexpand can then be used to unravel each
directory and call a second, user-supplied routine for each entry found. Dgwexpand does not
recognize the concept of links between entries in directories. Each entry is treated as a
separate entity.
Dgwalk can function in two modes: ad hoc and all. The basic difference between the two
modes stems from the fact that the Amoeba name server implements a directed graph. The
consequence is that a certain directory might have multiple paths into it with differing rights.
In all mode dgwalk first tries to obtain all available information on all reachable directories
and then calls a user-supplied function for every directory found. In ad-hoc mode, the
default, dgwalk calls the user-supplied function as soon as each directory is found and
expects a return value from that function indicating whether that directory has been
satisfactorily dealt with or not. If the directory has been satisfactorily dealt with, dgwalk will
not call the user-supplied function again for that directory, even if a path with more rights is
found. If the directory has not been satisfactorily dealt with, dgwalk will call the user-
supplied function again whenever the directory capability is met again. For directories not
met again dgwalk will call a third user-supplied function, thereby indicating that dgwalk
expects that this is the last time a user-supplied function is called for that entry. Dgwalk will
keep calling the user-supplied functions for each directory not yet satisfactorily dealt with.
It is possible to construct graphs in which certain parts of the graph might not be found in
ad-hoc mode.
dgwalk
errstat
dgwalk(params)
dgw params params;
The parameters to dgwalk are passed in a structure. This structure has the following
definition:
dgwexpand
errstat
dgwexpand(path, proc)
dgw paths path;
void (*proc)();
This function can be called to examine the contents of a directory. The directory to be
examined is specified by the path parameter that could be passed to dgwexpand by one of the
parameter functions of dgwalk. Dgwexpand simply uses the first path in the list to get the
contents of the directory and calls proc for each entry found. The function specified by proc
is called with four parameters:
visit entry(path, cset, parent cset, entry)
char *path;
capset *cset;
capset *parent cset;
char *entry;
Cset is the capability-set found under entry in the directory specified by parent cset . Path
gives the string with the full path name that can be used to access the entry.
Diagnostics
Both dgwalk and dgwexpand can return any of the Amoeba error messages. If they return
STD NOMEM there is a fair chance that they ran out of memory, but it might also indicate that
one of the servers involved ran out of memory.
See Also
om(A).
Name
direct − capability-based functions for searching in and modifying directories
Synopsis
#include "module/direct.h"
Description
This module provides a portable interface to directory servers. It has the advantage of being
independent of the particular directory server in use, but it does not make available all the
functionality of every directory server, e.g., the Soap Server. It is similar to the module
containing the same set of functions but with the prefix ‘‘name ’’ instead of ‘‘dir ’’. The
main difference is that these functions take an extra argument, origin, which is the capability
for a directory relative to which the given name is to be interpreted (see below).
In each function, the name should be a ‘‘path name’’ that specifies a directory or directory
entry. To these functions, a directory is simply a finite mapping from character-string names
to capabilities. Each (name, capability) pair in the mapping is a ‘‘directory entry’’. The
capability can be for any object, including another directory; this allows arbitrary directory
graphs (the graphs are not required to be trees). A capability can be entered in multiple
directories or several times (under different names) in the same directory, resulting in
multiple links to the same object. Note that some directory servers may have more complex
notions of a directory, but all that is necessary in order to access them from this module is
that they satisfy the above rules.
A path name is a string of printable characters. It consists of a sequence of 0 or more
‘‘components’’, separated by ‘‘/’’ characters. Each component is a string of printable
characters not containing ‘‘/’’. As a special case, the path name may begin with a ‘‘/’’, in
which case the first component starts with the next character of the path name. Examples:
‘‘a/silly/path’’, ‘‘/profile/group/cosmo-33’’.
If the path name begins with a ‘‘/’’, it is an ‘‘absolute’’ path name, otherwise it is a
Errors
Most functions return the error status of the operation. Most of them perform transactions, so
in addition to the errors described below, they may return any of the standard RPC error
codes.
STD OK: the operation succeeded.
STD CAPBAD: an invalid capability was used.
STD DENIED: one of the capabilities encountered while parsing the path name had
insufficient access rights (for example, the directory in which the
capability is to be installed is unwritable).
STD NOTFOUND: one of the components encountered while parsing the path name does not
exist in the directory specified by the preceding components (the last
component need not exist for some of the functions, but the other
components must refer to existing directory entries).
Functions
dir append
errstat
dir append(origin, name, object)
capability *origin;
char *name;
capability *object;
Dir append adds the object capability to a directory server under name (interpreted relative
Error Conditions:
STD EXISTS: name already exists
dir breakpath
char *
dir breakpath(origin, name, dir)
capability *origin;
char *name;
capability *dir;
Dir breakpath stores in dir the capability for the directory allegedly containing the object
specified by name (interpreted relative to origin). It returns the simple name of the entry
under which the object is stored, or would be stored if it existed. It is intended for locating
the directory that must be modified to install an object in a directory server under the given
name . In detail, dir breakpath does the following:
If the name is a path name containing only one component, dir breakpath stores the
capability for either the origin (if the path name is relative) or the user’s root directory (if the
path name is absolute) in dir, and returns the name (without any leading ‘‘/’’).
Otherwise, the name is parsed into two path names, the first consisting of all but the last
component and the second a relative path name consisting of just the last component.
Dir breakpath stores the capability for the directory specified by the first in dir and returns
the second. The first path name must refer to an existing directory.
dir close
errstat
dir close(dp)
struct dir open *dp;
Dir close is called after reading a directory with dir open and dir next . It frees up the
storage associated with the open directory specified by dp, which must be a value returned by
a previous call to dir open. The directory is now ‘‘closed’’ and any subsequent attempt to
use dp will produce undefined behavior.
Error Conditions:
STD EXISTS: name already exists in the directory server
dir delete
errstat
dir delete(origin, name)
capability *origin;
char *name;
Dir delete deletes the entry specified by name (interpreted relative to origin) from a
directory server. The object specified by the capability associated with name in the directory
entry is not affected. If desired, it should be separately destroyed (see std destroy (L)).
Required Rights:
SP MODRGT in the directory that is to be modified
dir lookup
errstat
dir lookup(origin, name, object)
capability *origin;
char *name;
capability *object;
Dir lookup finds the capability named by name (interpreted relative to origin) and stores it
in object .
dir open
struct dir open *
dir open(dir)
capability *dir;
Dir open is called to ‘‘open’’ the directory specified by dir, in preparation for listing it. It
returns a pointer to a temporary data structure that keeps track of a current position in the
open directory. The names entered in the directory can subsequently be retrieved by calling
dir next with the value returned by dir open. After listing the directory it should be closed
with dir close . Multiple directories can be open at the same time.
dir origin
capability *
dir origin(name)
char *name
If name is an absolute path name, dir origin returns the capability for the user’s root
directory. Otherwise it returns the capability for the current working directory which it
obtains from the capability environment. This function is used to obtain an initial value for
the origin parameter for the other functions in this module.
dir replace
errstat
dir replace(origin, name, object)
capability *origin;
char *name;
capability *object;
Dir replace replaces the current capability stored under name (interpreted relative to origin)
with the specified object capability. The name must refer to an existing directory entry.
This directory entry is changed to refer to the specified object capability as an atomic action.
The entry is not first deleted then appended.
Required Rights:
SP MODRGT in the directory that is to be modified
dir root
errstat
dir root(origin)
capability *origin;
Dir root stores the capability for the user’s root directory in origin. It returns 0 on success,
-1 on failure.
Error Conditions:
−1: cannot find the ROOT capability in the environment
Examples
capability rootcap, modify dir, object;
char *old name = "/home/abc";
/* Change the name /home/abc to /home/xyz: */
if (dir root(&rootcap) == 0) {
char *entry name =
dir breakpath(&rootcap, old name, &modify dir);
if (entry name &&
dir lookup(&modify dir, entry name, &object) == STD OK &&
dir delete(&modify dir, entry name) == STD OK) {
if (dir append(&modify dir, "xyz", &object) == STD OK) {
fprintf(stderr, "Successful name change\n");
return;
}
}
}
fprintf(stderr, "Error -- no name change\n");
Several more examples can be found in the source code for the name-based directory
functions (see name (L)), since they are implemented as little more than calls to this module.
See Also
name(L).
Name
environment − lookup, delete and modify string environment variables
Synopsis
void env delete(env, name)
char *env lookup(env, name)
char **env put(env, env alloc, newmap, override)
Description
This module is used for manipulating string environments. A string environment is just a
mapping from character string names to character string values. Each process has a standard
string environment, pointed to by the global environ variable.
These routines take the environment pointer as a parameter so that they can operate on any
environment, not just the standard environment. The environment pointer should point to a
NULL-terminated array of pointers to NULL-terminated character strings. Each string
should have the form ‘‘name=value’’ (where ‘‘name’’ does not contain ‘‘=’’), specifying that
‘‘name’’ maps to ‘‘value’’. No two strings in the same environment should use the same
name. (These routines will not add a duplicate name but it is possible to build such an
environment by hand.)
env delete
void
env delete(env, name)
char **env;
char *name;
This function deletes the all environment variables with name name from the environment
pointed to by env. If name is not in the environment then the environment will be
unchanged.
env lookup
char *
env lookup(env, name)
char **env;
char *name;
This routine looks up the environment variable with name name in the environment env and
returns a pointer to the value associated with that name. (I.e., the part after the ‘‘=’’ in the
environment string.) It returns the NULL-pointer if name was not found in the environment.
See Also
exec file(L), getcap(L).
Name
error − return names of standard exceptions and standard errors
Synopsis
#include "exception.h"
char *
exc name(sig)
signum sig;
#include "stderr.h"
char *
err why(err)
errstat err;
Description
Exc name returns a pointer to a static string describing the exception specified by sig.
Err why returns a pointer to a static string describing the error specified by err.
They only know about the more common errors and exceptions, and return a generic
description, such as
amoeba error −842780
in other cases. More specific errors are covered by other functions, such as tape why.
Warnings
Both exc name and err why use an internal buffer to store their generic descriptions. This
buffer will be overwritten by a subsequent call, possibly by another thread.
Example
The function find cap tries to find a capability. It prints a descriptive message and exits if it
cannot.
See Also
exception(H), rpc(L), signals(L), tape(L).
Synopsis
#include "exception.h"
Description
This manual page offers a quick reference to the exceptions that a thread can generate.
Exception handling is treated in signals(L).
The names for exceptions are defined in the include file exception.h. They are:
EXC ILL Illegal instruction.
EXC ODD Mis-aligned memory reference.
EXC MEM Access to non-existent memory. Note: on some machines this exception is not
generated; rather EXC ACC is generated instead.
EXC BPT Breakpoint instruction or trace-mode exception.
EXC INS Undefined instruction.
EXC DIV Divide by zero (or other arithmetic trap).
EXC FPE Floating point exception.
EXC ACC Memory access violation (e.g. writing read-only memory).
EXC SYS Bad system call or illegal system call arguments.
EXC ARG Illegal instruction operand. On some machines this shows up as EXC INS.
EXC EMU System call emulation trap.
EXC ABT The library function abort was called (see ansi C(L), posix(L)).
Exception number zero (EXC NONE) is reserved. It is used as a list terminator in the vector
passed by sys setvec (see signals(L)).
Name
exec file − start execution of a process
Synopsis
#include "amoeba.h"
#include "module/proc.h"
#include "am exerrors.h"
errstat exec pd (
process d *pd,
int pdlen,
capability *host,
capability *owner,
int stacksize,
char **argv,
char **envp,
struct caplist *caps,
capability *process ret
);
Description
The description of the process interface is split into several parts. The process descriptor data
structure and its basic access methods are described in process d(L). The low-level kernel
process server interface is described in process (L). The high-level process execution
interface is described here. See also pd read(L).
Exec file starts the execution of a new process. It takes many arguments, allowing precise
control over the execution, but chooses sensible defaults for input parameters that are zero or
NULL-pointers. Its first argument can be the capability of either an executable file or of a
directory . The latter is used to implement heterogeneous process startup (i.e., executing a
program without prescribing the architecture on which it is to be run). The directory should
contain a set of one or more binaries, each for a different architecture. Of course, in order for
Example
To execute the file a.out in a default environment, the following call suffices:
static char *arglist[] = {"a.out", (char *)0};
capability process;
errstat err;
See Also
ainstall(U), ax(U), buildstack(L), exec findhost(L), getcap(L), pd read(L), process(L),
process d(L), rpc(L).
Name
exec findhost − find a suitable pool processor (host) to execute a process
Synopsis
#include "amoeba.h"
#include "module/proc.h"
#include "exec fndhost.h"
Description
Exec findhost finds a pool processor suitable for executing the specified process descriptor
(see process (L) and process d(L)). It is passed a process descriptor so it can use the
architecture and required memory space to decide which processors are suitable.
Exec multi findhost is available for supporting heterogeneous process startup (i.e.,
executing a program without prescribing the architecture on which it is to be run). Also, it
gives the caller more control over its operation by making it possible to specify a non-default
run server or pool directory.
Exec multi findhost (and exec findhost, which is implied whenever exec multi findhost is
mentioned here) uses two strategies to find a suitable host. If possible, the run server (see
run(A)) is asked for a suitable host. The run server performs load balancing for all users and
all pool processors, so it can assign the most suitable host for the process (given what little it
knows about the process) and distribute processes evenly over the available hosts. This takes
at most two transactions: one to look up the capability of the run server (which is cached for
future use), and one to ask the run server for a host.
If the run server’s capability cannot be found, or if it does not respond in a timely fashion,
exec multi findhost scans the pool directory to find a running host that has the architecture
of one of the process descriptors provided. When called multiple times in the same process, a
host will be chosen in a round-robin fashion. The first host is chosen at random to avoid the
Error Conditions:
FPE BADARCH Cannot determine architecture
FPE NOPOOLDIR Cannot find pool directory
FPE BADPOOLDIR Cannot open pool directory
FPE NONE No suitable processor in pool directory
These constants are defined in exec fndhost.h.
Warning
Even when exec multi findhost thinks that a host is suitable, attempts to execute the given
process on that host may fail for a variety of reasons (e.g., memory fragmentation being the
most frequent cause, or processes on the host might have allocated extra memory in the
meantime).
Example
The following function implements the functionality of exec findhost using
exec multi findhost.
See Also
exec file(L), process(L), process d(L), run(A).
Name
exitprocess − terminate a process
Synopsis
#include "module/proc.h"
void exitprocess(status)
int status;
Description
Exitprocess terminates the current process. The value status is the so-called exit status, and
is passed to the parent. If the process has more threads the other threads are aborted, just as if
a pro stun had been done (see process (L)). As soon as all threads have terminated a dummy
process descriptor (see process d(L)) is created, consisting only of the fixed length part with
no thread map and no segment map. This process descriptor is sent to the owner, with a
reason of TERM NORMAL, and the exit status as detail.
When the last thread of a process calls exitthread (see sys newthread (L)) or thread exit (see
thread(L)), this call behaves as if exitprocess(0) had been called.
Warnings
Most user programs should call exit (see ansi C(L)) or exit (see posix(L)), not exitprocess .
Those functions do some additional cleanup like properly closing files before they terminate
the process.
See Also
process(L), process d(L), posix(L), ansi C(L), sys newthread(L), thread(L).
Name
findhole − search for unused space in virtual memory
Synopsis
#include "module/proc.h"
char *
findhole(size)
long size;
Description
Findhole retrieves the virtual memory layout (using getinfo (L)) and looks for a place where a
segment of size bytes can be mapped in, without overlapping other segments, and with some
unmapped space (typically 32K) on each side. It returns the address where the segment
should be mapped.
Diagnostics
Findhole returns zero if the getinfo call failed or if no fitting hole was found.
Warnings
There is no locking mechanism, so it is conceivable that another task fills the hole with
something before this task has had a chance.
This interface should be replaced by one that finds a hole and maps a segment in.
See Also
getinfo(L), malloc(L), seg map(L).
Name
fs − stream interface (OBSOLETE)
Synopsis
#include "file.h"
Description
This interface is implemented by the UNIX-emulation pipe-server and by native Amoeba tty-
servers.
Fsread reads size bytes from the stream pointed to by cap, starting from position position,
and puts them in buf.
Fswrite writes size bytes from buf to the stream pointed to by cap, starting from offset
position.
Diagnostics
If an error occurred, both functions will return the (negative) code. Otherwise they return the
number of bytes actually copied. Which errors can be returned obviously depends on the
server that manages cap.
Warnings
The offset parameters are currently ignored by every server that implements the fs-interface.
They descend from an age in which the file interface was identical with the stream interface.
Example
This function fsreads an object and copies it onto another. It returns 0 on failure, and 1 on
success. Note that this will not work for files, only for tty’s and pipes.
copy(in, out)
capability *in, *out;
{
char buf[200];
long position = 0;
int done = 0;
do {
long r, w;/* #bytes read, written */
r = fsread(in, position, buf, (long)sizeof(buf));
if (r < 0) {
fprintf(stderr, "fsread failed: %s\n", err why(r));
return 0;
}
if (r == 0) done = 1;
else {
w = fswrite(out, position, buf, r);
if (w < 0) {
fprintf(stderr, "fswrite failed: %s\n", err why(w));
return 0;
}
assert(r == w);
position += r;
}
} while (!done);
return 1;
}
Name
getcap − get environment capability
Synopsis
#include "amoeba.h"
capability *
getcap(name)
char name[];
Description
Getcap returns a pointer to the environment capability name .
Diagnostics
Getcap returns NULL when no capability name exists.
Environment Capabilities
Here is a list of commonly used environment capabilities:
ROOT The root directory
WORK The working directory
STDIN Standard input
STDOUT Standard output
STDERR Diagnostics output
SESSION The session server
TTY The current terminal
Warnings
There is no capability environment under UNIX so getcap always returns NULL.
Example
To find the capability for ’/’:
capability *slashcap;
slashcap = getcap("ROOT");
See Also
envcap(U).
Name
getinfo − get information about the current process
Synopsis
#include "amoeba.h"
#include "module/proc.h"
Description
Getinfo returns information about the calling process. If retcap is not a NULL-pointer the
capability for the current process (with all right bits set) will be stored there. If len is non-
zero procbuf should point to an area of len bytes. Upon return, procbuf will be filled with a
stripped-down process descriptor for the current process. The process descriptor contains all
the static information and the segment map, but it does not contain a thread map. The
process descriptor is returned in native byte order. See process d(L) for a description of the
process descriptor. Getinfo returns the size of the process descriptor.
Note that the segment identifiers used by seg map and friends (see seg map(L)) are indices
into the array of segment descriptors returned as part of the process descriptor.
Warnings
If procbuf is not big enough to contain the complete segment mapping table no segments will
be returned at all. So, if getinfo is used to obtain a segment map the value of pd nseg should
be checked to see whether the call was successful.
Example
The following program uses getinfo to get a segment map, searches for the segment that
contains the stack of the current thread and unmaps that segment. It is left to the imagination
what will happen after the seg unmap call returns.
main() {
char pdbuf[MAXPD];
process d *pd = (process d *)pdbuf;
segment d *sd;
segid i, seg to unmap;
char c;
See Also
process d(L), seg map(L).
Name
goodport − routines for avoiding repeated transactions to bad ports
Synopsis
#include "amoeba.h"
#include "module/goodport.h"
gp badport(port, command)
gp notebad(cap, status)
gp trans(cap, func call)
gp std copy(server, source, target)
gp std destroy(cap)
gp std info(cap, buf, n, len)
gp std restrict(cap, mask, new)
gp std status(cap, buf, n, len)
gp std touch(cap)
Description
One of the problems with the client-server model is that it is possible that a server for a
particular type of object is unavailable. Every attempt to communicate with that server will
result in a time-out while an attempt is made to locate the server. A succession of time-outs
can result in very long delays and seriously impact perceived and actual performance. This
module attempts to keep a history of servers which have recently been unavailable. It does
this by remembering ports for which a locate time-out has occurred. By first checking to see
if a port is in the list of unavailable ports it is possible to avoid the long delays. If an object is
replicated then it is possible to immediately see if a particular port is known to be not
responding and first attempt to obtain the object from one of the alternative servers for the
object.
A port is defined to be bad if a previous transaction with that port has returned the error
RPC NOTFOUND. That is, an attempt to locate the server with that port has failed.
A fixed size cache of bad ports is maintained. If more bad ports are found than fit in the
cache then the least recently added entry in the cache is deleted to make space for a new
entry.
Types
The set of legal values for the command argument of gp badport are defined in goodport.h.
gp notebad
errstat
gp notebad(cap, status)
capability *cap;
errstat status;
Gp notebad is a utility routine to add a port to the bad port cache. If a transaction returned
an error status then the capability cap used for the transaction and the error returned status
can be given to this routine. If status is RPC NOTFOUND this routine will add the port of the
server to the bad port cache. The function returns status.
gp trans
errstat
gp trans(cap, func call)
capability *cap;
errstat (*func call)();
Gp trans is a macro defined in goodport.h that calls the function func call if the port of the
capability cap is not in the bad port cache. If it is in the cache then it returns the error
RPC BADPORT.
If the result of the func call is RPC NOTFOUND then it registers the port of the server in the
bad port cache. It returns the error status of func call.
This macro has been used to define several other macros for all the std functions that
automatically log any bad ports in the bad port cache. These routines have the same
parameters as the routines described in std(L) but the function names are prefixed with gp .
Name
grp − the group communication primitives
Synopsis
#include "amoeba.h"
#include "group.h"
Description
RPC provides point-to-point communication between a single client and a single server.
What is often needed is 1-to-n communication (for example in a replicated server). This can
be simulated with n−1 RPCs but this is not very efficient. In most systems this will cost at
least 2 (n−1) network packets (one packet for the message and one for the
acknowledgement). If the message is larger than a single packet the cost will be even higher.
Therefore a more efficient system of message passing for groups of processes has been
provided. It provides optional fault tolerance and a total ordering of messages. Where
possible it takes advantage of hardware multicast support (e.g., on Ethernet). Where no
support is available it falls back to using point-to-point messages to send the information.
A group consists of one or more processes, called members , typically running on different
processors and cooperating to provide some service or to implement some application
program. Processes may be a member of more than one group. Groups are closed, which
means that only group members can send a broadcast message to the group. Processes which
are not a member and which wish to communicate with a group can use RPC (or can join the
group).
A group is identified by a port. All group calls must supply this port. A group is explicitly
created by calling grp create . The process that called this primitive is the first member of
the group. It becomes the sequencer for the group, ensuring a total ordering of messages and
maintaining message history for use in recovery in the event of failures. Other processes can
become a member by calling grp join, and supplying as parameter the port with which the
group has been created. There are a number of ways in which the creator of the group can
pass the port to other processes so that they can join the group. One way is to publish the
port in the directory server so that other processes can look it up. Another way is to use the
program gax(U) which passes the port to a process through its capability environment. The
Error codes
The possible error codes returned by the group primitives, and their interpretation are shown
in the next table:
Note that the primitives grp create , grp join grp receive and grp reset return a signed
integer rather than the standard error type errstat. In these cases the call must be considered
successful when a non-negative integer is returned. Otherwise the return value should be
interpreted as an error code.
grp create
g id t
grp create(p, resilience, maxgroup, lognbuf, logmaxmess)
port * p;
g indx t resilience;
g indx t maxgroup;
uint32 lognbuf;
uint32 logmaxmess;
The primitive grp create creates a new group. The parameter resilience specifies how many
member failures must be tolerated without loss of any message. Thus, if after resilience
crashes, the group is rebuilt with grp reset it is still guaranteed that the remaining members
will still receive all the messages sent to the group and that they still will receive them in the
same order. Consider a group with resilience equal to zero. In this case, if a member
successfully sends a message to the group and then crashes, it is not guaranteed that the
remaining members will receive this messages after they have rebuilt the group. The
parameter maxgroup limits the total number of group members permitted. The value of
resilience may be greater than the maximum number of group members maxgroup but see
the description of grp send below for a discussion of this. Parameters lognbuf and
logmaxmess specify the amount and size of message buffers that are to be allocated by the
kernel of each member. They are both 2-logs of the actual amount. (Note that the actual
buffers allocated are slightly larger than the size specified to allow space for the addition of a
header to the message. The caller can thus send messages up to and including the specified
buffer sizes.)
Regarding the parameter values, the following restrictions apply:
- resilience must be less than 32.
- lognbuf depends on size of the group (nbuf should be bigger than the number of
members) but must be at least 4.
- logmaxmess must be at least 10 (i.e., maxmess should be at least 1K).
- maxgroup must be at least 1. Since the group protocol sometimes sends global state
information to all members (e.g., during the join and recovery stage), the parameter
logmaxmess may have to be increased when the number of members becomes larger
than about 30.
If a process already is a member of a group, it is not allowed to create another group with the
same port. If multiple processes create a group with the same port, no error is returned. If a
process does a join on that port, it will become member of one group at random. This is
similar to multiple servers performing a getreq on the same port. A request sent to that port
will be received by one of the servers at random. Messages sent to that group are only
received by members of that group. They will not go to other groups listening to the same
port.
grp info
typedef struct {
g seqcnt t st expect; /* next seq # to be delivered */
g seqcnt t st nextseqno; /* next seq # to be received */
g seqcnt t st unstable; /* next seq # to be acked */
g index t st total; /* total number of members */
g index t st myid; /* my member identifier */
g index t st seqid; /* sequencer identifier */
} grpstate t, *grpstate p;
errstat
grp info(gid, p, state, memlist, size)
g id t gid;
port * p;
grpstate p state;
g indx t memlist[];
g indx t size;
The grp info primitive allows a group member to acquire information stored in the kernel
about a group. If it succeeds it will return in the output structure state the number of
members in the group, the member identifier of the caller and the sequence number of the
next message expected. The identifiers of the members in the group are returned in the array
memlist having size entries. The memlist can be used, for example, to find out which
grp join
g id t
grp join(hdr)
header p hdr;
Once a group has been created, other processes can become members of it by executing
grp join. Only a member can receive messages that are sent to its group. The group to be
joined is specified by the port contained in the header hdr. Like grp create , grp join returns
a group descriptor for use in subsequent group calls. In addition to adding a process to a
group, grp join also delivers the header hdr to all other members (excluding itself). This
way, other members can find out that a new member has joined the group. The port of the
group should be stored in h port, when calling grp join. A process is not allowed to join a
group where it is already a member.
grp leave
errstat
grp leave(gid, hdr)
g id t gid;
header p hdr;
Once a process is a member of a group, it can leave the group by calling grp leave . After a
member has left the group, it will not receive subsequent broadcasts. In addition to leaving
the group, grp leave delivers hdr to all members (including the leaving member itself). This
way, all members can find out that a member has left. The member receives its own
message, so that it can check if it has processed all the messages until its leave message.
After a member has left the group, it can join the group again. Like in all other group
primitives that have a header parameter, the port in the header should be equal to the group’s
port. When the last member of a group calls grp leave , the group ceases to exist.
grp receive
int32
grp receive(gid, hdr, buf, size, more)
g id t gid;
header p hdr;
bufptr buf;
uint32 size;
int * more;
To receive a broadcast, a member must call grp receive. If a broadcast arrives and no such
primitive is outstanding, the message is buffered. When the member finally does a
grp receive, it will get the next one in sequence. The parameters hdr, buf and size specify
the header and the buffer in which the message should be delivered. If size is smaller than
grp reset
g indx t
grp reset(gid, hdr, nmem)
g id t gid;
header p hdr;
g indx t nmem;
The primitive grp reset allows recovery from member crashes. If one of the members (or its
kernel) does not respond to messages, the protocol enters a recovery mode. All outstanding
calls return an error value (BC ABORT) indicating that a member has crashed. The user
application can now call grp reset to transform the group into a new group that contains as
many live members from the group as possible. This does not change the gid. Calling
grp reset is illegal when the group is not in recovery mode. The error BC ILLRESET will
be returned in this case.
The parameter nmem specifies the number of members that the new group must contain as a
minimum. If fewer than nmem members of the group are still available then BC FAIL will
be returned. In this case it is possible to attempt another grp reset with perhaps another
value for nmem or perhaps at a later time when the network is restored. The group is not
deleted when a reset attempt fails.
When grp reset succeeds, it returns the number of members in the recovered group.
In addition to recovering from crashes, grp reset delivers hdr to all newly recovered
members. It may happen that multiple members initiate a recovery at the same moment. The
new group consisting of the members that can communicate with each other, however, is
built only once.
The way recovery is done is based on the design principle that policy and mechanism should
be separated. In many systems that deal with fault tolerance, recovery from processor
crashes is completely invisible to the user application. Here it is done differently. A parallel
application that multiplies two matrices, for example, may want to continue even if only one
processor is left. A banking system may require, however, that at least half of the group is
alive. The user is able to decide on the policy. The group primitives only provide the
mechanism.
grp set
errstat
grp set(gid, p, sync, reply, alive, large)
g id t gid;
port * p;
interval sync;
interval reply;
interval alive;
uint32 large;
The grp set primitive can be used to set a few parameters used in the implementation of
groups. It is mainly present for testing and performance analysis.
The three interval parameters influence the rate at which members are synchronized. Sync
determines how often the sequencer checks if the other members are up to date. Reply
determines how soon a message is retransmitted. Alive determines how often members
check each other to see if they are alive. The parameter large specifies above which message
size a sending member itself should broadcast the message. Normally, a message to be
broadcast is sent to the member responsible for ordering the messages. Letting the message
be broadcast by the sending member has the advantage that the message only appears once on
Example
An example of a working program can be found in the source code in the file
src/test/performance/group/grp perf.c . A brief explanation of it is given below as a quick
introduction.
The normal way to start a group program is with gax(U). It passes numerous command line
arguments to a group program, including the number of group members (ncpu), the desired
resilience (resilience) and which member of the group the current process is (cpu). These
arguments need to be processed. It also provides several capabilities in the capability
environment. The GROUPCAP tells which port the group will listen to.
capability *groupcap;
port group;
The routine test() in grp perf.c creates the group and carries out the performance
measurements. The member numbered 0 actually creates the group. The other members join
the group after it has been created. Once all the members are present it can proceed with its
work.
/* Start group. */
if (cpu == 0) {
gd = grp create(&group, (g indx t) resilience, ncpu,
(uint32) LOGBUF, logdata);
if (gd < 0)
panic("create failed (%d)0, (int) gd);
} else {
if ((gd=grp join(&hdr)) < 0)
panic("%d: join failed %d0, cpu, gd);
}
The next step is to set some parameters and wait until all the expected group members are
ready to do work. The variable state.st total below reflects the total number of members in
the group. The handle() function looks at each message received, in particular at group join
requests, and keeps the state up to date.
Following this a thread is created to accept all incoming messages. It executes the routine
daemon() which consists of the same basic loop above: grp receive() followed by a call to
handle(). It also has additional code to deal with recovery after the loss of group members.
Thereafter, one or more members send a set number of messages using the grp send()
primitive. It times how long it takes using sys milli(). To ensure that the daemon() thread is
scheduled the routine threadswitch() is called regularly. If the messages are not read fairly
quickly after arrival, the group can choke up waiting for the ‘‘slow’’ member. Another
solution to this is to enable preemptive scheduling and give the reader thread equal or higher
Once all the message timing has been completed it is necessary to terminate the group
cleanly. This is done using grp leave() .
hdr.h command = LEAVE;
hdr.h size = cpu;
hdr.h extra = my id;
s = grp leave(gd, &hdr);
Once test() terminates the daemon() thread will receive the group leave command and
terminate as well. Since it is the last active thread the process will then terminate.
See Also
gax(U), rpc(L).
Name
head − C run-time start-off
Synopsis
#include "amoeba.h"
#include "caplist.h"
stackfix(sp)
Description
This document describes the way the C main program is called. The main routine is called
with four arguments:
argc is the number of arguments;
argv is an array of strings containing the arguments, with argv[0] the name of the program;
envp is a NULL-terminated array of strings containing the string environment (all exported
shell variables, usually);
capv is unique to Amoeba and contains the capability environment. See getcap (L) for a
description of standard capability environment entries.
Since the string and capability environment are often needed by other library modules their
addresses are also stored in the global variables environ and capv, respectively. In the light
of compatibility with STD C, this is the recommended way of accessing them.
Stack format
The format of the stack segment ‘on the wire’, i.e., as it is created by buildstack (L), is in the
byte order of the host that built it. From high to low addresses, the stack contains the
capabilities, the names of the capabilities, the NULL-terminated capv array (with pointers to
the previous items), the environment strings, the NULL-terminated envp array (with pointers
to the environment strings), the argument strings, the argv array (with pointers to the
argument strings), a pointer to the capv array, a pointer to the envp array, a pointer to the
argv array and the argument count. Note that all addresses are real pointers, so the address
where the stack segment will be mapped has to be known beforehand.
Since the stack is created in the byte-order of the originating machine it may have to be fixed
Implementation
The run-time start-off routine is very simple. First, it executes some architecture-dependent
code to setup registers and terminate the frame pointer chain. Next, it calls stackfix to fix
the stack. Then it calls main with the aforementioned arguments. Finally it calls exit (see
ansi C(L)) with the return value from main as argument.
See Also
buildstack(L), exitprocess(L), getcap(L), ansi C(L).
Name
host lookup − look up the capability for a host
Synopsis
#include "module/host.h"
Description
These routines search for the capability of the host specified by the string hostname . They
look in various places as described below. If the lookup is successful they return STD OK
and the capability for the host is in *cap. Otherwise they return the error status and *cap is
not set.
host lookup
errstat
host lookup(hostname, cap)
char *hostname;
capability *cap;
Host lookup begins by calling super host lookup. If that succeeds it returns the value of
super host lookup. Otherwise it looks in the directory HOST DIR as defined in the file
ip host lookup
errstat
ip host lookup(hostname, extension, cap)
char *hostname;
char *extension;
capability *cap;
This function is used to look up one of the capabilities of an IP server (see ipsvr(A)). It uses
host lookup to find the capability for the specified host, which should be a host running an IP
server. It then uses the extension to select the desired element of the server. The extension is
a string which is either ip, eth, tcp or udp. Any other values will fail.
The function returns STD OK on success. Otherwise it returns one of the standard error
codes indicating the cause of failure.
Examples
The following code looks up the capability of the machine called ihnp4.
char * hostname = "ihnp4"
capability hcap;
errstat err;
if ((err = host lookup(hostname, &hcap)) == STD OK)
/* use the capability for something */;
else
printf("lookup of %s failed: %s\n", hostname, err why(err));
To look up the tcp capability of the IP server running on the host armada1E use:
char * hostname = "armada1E";
capability tcpcap;
errstat err;
if ((err = ip host lookup(hostname, "tcp", &tcpcap)) == STD OK)
/* use the capability for something */;
else
printf("ip lookup of %s.%s failed: %s\n",
hostname, "tcp", err why(err));
Name
ip − Internet Protocol server’s general-purpose client interface stubs
Synopsis
#include "stddef.h"
#include "amoeba.h"
#include "server/ip/hton.h"
#include "server/ip/gen/oneCsum.h"
#include "server/ip/tcpip.h"
Description
The Internet Protocol (IP) server implements four network protocols, as described in
ipsvr(A). The four supported protocols are ETH, IP, TCP and UDP. The general-purpose
routines described here and the protocol specific routines described in ip eth(L), ip ip(L),
ip tcp(L) and ip udp(L) give access to the protocols of the IP server. Access to these
services is provided using two types of capabilities: server capabilities and channel
capabilities. The server capabilities are called eth, ip, tcp, udp, which correspond to the
ETH, IP, TCP and UDP interfaces, respectively. The server capabilities can be used to
obtain a channel to the corresponding server. This is done with tcpip open. The channel
capability can be used to transfer data using the protocol implemented by the server. This
Types (general)
server/ip/types.h
defines u8 t, u16 t, u32 t and i32 t (and U8 t, U16 t, U32 t and I32 t for use in
prototypes).
Rights
The following rights are defined in server/ip/tcpip.h :
IP RIGHTS OPEN The right to do a tcpip open (this indicates a server capability).
IP RIGHTS RWIO The right to do I/O (this indicates a channel capability).
IP RIGHTS DESTROY The right to destroy a connection.
IP RIGHTS LINGER The right keeps the server from destroying the connection when
it is not used for some time.
IP RIGHTS SUPER The right to get existing capabilities (using tcpip mkcap ).
u32 t
htonl(host dword)
u32 t host dword;
u16 t
ntohs(network word)
u16 t network word;
u32 t
ntohl(network word)
u32 t network word;
These macros convert 16-bit and 32-bit quantities to and from the network byte order used by
the TCP/IP protocols. The function of the macros is encoded in their name. H means host
General Functions
oneC sum
u16 t
oneC sum(prev, data, size)
u16 t prev;
u16 t *data;
size t size;
OneC sum is used to calculate the one’s complement checksum needed for IP network
packets. The IP checksum is described in RFC-1071 (Computing the Internet checksum).
One Csum expects three parameters:
prev The checksum of previous blocks of data that are to be included in the checksum.
The value of prev in first call to oneC sum should be 0.
data A pointer to the block of data. The data is interpreted as a series of 16-bit
numbers in network byte order, but an odd number of bytes is also allowed.
size The size of the data in bytes.
tcpip why
char *
tcpip why(err)
errstat err;
This routine returns a pointer to a statically allocated string describing the error code err. If
err is not one of the errors as described in the Diagnostics section below then tcpip why will
return the same string as err why (see error(L)).
tcpip mkcap
errstat
tcpip mkcap(tcpip cap, obj, cap)
capability *tcpip cap;
objnum obj;
capability *cap;
A channel capability can be lost, or can be kept inside a process. Tcpip mkcap recreates the
capability which has object number obj. This provides a way to obtain an otherwise lost
capability. To work out the object number of a particular channel use the std status(U)
command.
tcpip read
errstat
tcpip read(chan cap, buffer, bytes)
capability *chan cap;
char *buffer;
size t bytes;
Tcpip read transfers data from the TCP/IP server to the client. The call blocks until enough
data is available. The semantics of tcpip read are different for each of the servers. See the
tcpip read section in the ip xxx(L) manual pages for the exact semantics.
tcpip write
errstat
tcpip write(chan cap, buffer, bytes)
capability *chan cap;
char *buffer;
size t bytes;
Tcpip write transfers data from the client to the TCP/IP server. The call blocks until enough
buffer space is available. The semantics of tcpip write are different for each of the servers.
See the tcpip write section in the ip xxx(L) manual pages for the exact semantics.
Diagnostics
The TCP/IP server introduces several new error codes. These are defined in
server/ip/tcpip.h .
TCPIP PACKSIZE This indicates an attempt to read (tcpip read) or write
(tcpip write) with a buffer that is too large or too small.
TCPIP OUTOFBUFS The TCP/IP server has insufficient memory to execute the
request.
TCPIP BADIOCTL This indicates an attempt to execute a command the particular
server does not understand. For example, a tcp ioc getconf on
an ETH channel.
TCPIP BADMODE The request was refused because the channel is not fully
configured, in the wrong state or the parameters are invalid.
See Also
error(L), ip eth(L), ip ip(L), ip tcp(L), ip udp(L), ipsvr(A).
Name
ip eth − Internet Protocol server’s Ethernet client interface stubs
Synopsis
#include "amoeba.h"
#include "server/ip/eth io.h"
#include "server/ip/types.h"
#include "server/ip/gen/ether.h"
#include "server/ip/gen/eth io.h"
Description
The Internet Protocol (IP) server implements four network protocols, as described in
ipsvr(A). The routines described below give access to the Ethernet protocol of the IP server.
This allows reading and writing of raw Ethernet packets. Access to this service is provided
using two types of capabilities: a server capability and channel capabilities. The server
capability is called eth. It is used to obtain a channel to the corresponding server. This is
done with tcpip open (see ip(L)). The channel capability can be used to transfer data using
the protocol implemented by the server. This is also done with the generic tcpip read and
tcpip write routines. Since their semantics vary slightly depending on channel type, these
routines are described briefly below. The eth ioc routines are used to manage the options
and status of the ETH channel.
Before they are described, a brief introduction to the various types is given. The general-
purpose types and rights in the capabilities are described in ip(L).
Eth Types
server/ip/gen/ether.h
defines struct ether addr (ether addr t), ether type t and Ether type t for use in
prototypes.
server/ip/gen/eth io.h
defines struct nwio ethopt (nwio ethopt t) and struct nwio ethstat (nwio ethstat t)
server/ip/gen/eth hdr.h
defines struct eth hdr (eth hdr t)
tcpip read
errstat
tcpip read(chan cap, buffer, nbytes)
capability *chan cap;
char *buffer;
size t nbytes;
Tcpip read transfers Ethernet packets from the Ethernet channel specified by chan cap to
the client. The data is returned in buffer which has size nbytes . The call blocks until a
packet is available. If successful it returns the number of bytes read. If unsuccessful the
function returns an error status. It is possible to read just the data of the Ethernet packets or
both the header and the data. See eth ioc setopt below for details.
Only reads with a buffer size greater than or equal to the maximum packet size are allowed.
Error Conditions:
See ip(L) for a description of the TCPIP error codes.
TCPIP BADMODE
TCPIP PACKSIZE
STD INTR
tcpip write
errstat
tcpip write(chan cap, buffer, nbytes)
capability *chan cap;
char *buffer;
size t nbytes;
Tcpip write transfers the nbytes of data in buffer from the client to the Ethernet channel
specified by chan cap. The call blocks until enough buffer space is available. The buffer
must contain a complete packet and be at least of minimum size (60 bytes). On success this
function returns the number of bytes written. On error it returns a negative error status.
Error Conditions:
See ip(L) for a description of the TCPIP error codes.
TCPIP BADMODE
TCPIP PACKSIZE
STD INTR
Diagnostics
The complete set of error codes is described in ip(L).
See Also
error(L), ip(L), ip ip(L), ip tcp(L), ip udp(L), ipsvr(A).
Name
ip ip − Internet Protocol server’s IP client interface stubs
Synopsis
#include "amoeba.h"
#include "server/ip/ip io.h"
#include "server/ip/types.h"
#include "server/ip/gen/in.h"
#include "server/ip/gen/ip io.h"
#include "server/ip/gen/route.h"
Description
The Internet Protocol (IP) server implements four network protocols as described in
ipsvr(A). The routines described below give access to the IP protocol in the IP server.
Access to this service is provided using two types of capabilities: a server capability and
channel capabilities. The server capability is called ip. The server capability is used to
obtain a channel to the corresponding server. This is done with tcpip open (see ip(L)). The
channel capability can be used to transfer data using the protocol implemented by the server.
This can also be done with the generic tcpip read and tcpip write routines. Since their
semantics vary slightly depending on channel type, these routines are described briefly
below. The ip ioc routines are used to manage the options and status of the IP channel.
Before they are described, a brief introduction to the various types is given. The general-
purpose types and rights in the capabilities are described in ip(L).
IP Types
server/ip/gen/in.h
defines ipaddr t, ipproto t and struct ip hdropt (ip hdropt t).
server/ip/gen/ip io.h
defines struct nwio ipconf (nwio ipconf t) and struct nwio ipopt (nwio ipopt t)
server/ip/gen/ip hdr.h
defines struct ip hdr (ip hdr t)
server/ip/gen/route.h
defines struct nwio route (nwio route t)
tcpip read
errstat
tcpip read(chan cap, buffer, nbytes)
capability *chan cap;
char *buffer;
size t nbytes;
Tcpip read reads IP packets from the IP channel of a TCP/IP server, specified by chan cap,
into buffer , which has length nbytes . If the packet is larger than nbytes the packet is
truncated to the buffer size and the error TCPIP PACKSIZE will be returned. It blocks
until it has a complete IP packet. If successful it returns the number of bytes read. Otherwise
it returns a negative error status. If the read is interrupted the server returns STD INTR.
It is possible to read the data only or the data plus IP header. See ip ioc setconf below for
details.
Error Conditions:
See ip(L) for a description of the error codes.
TCPIP BADMODE
TCPIP PACKSIZE
STD INTR
tcpip write
errstat
tcpip write(chan cap, buffer, nbytes)
capability *chan cap;
char *buffer;
size t nbytes;
Tcpip write transfers the IP packet in buffer from the client to the TCP/IP server’s IP
channel specified by chan cap. The IP packet to be sent is nbytes long. It accepts only
complete packets. It computes the checksum for the packet and inserts it at the appropriate
place. The call blocks until enough buffer space for the whole packet is available in the
TCP/IP server. On success it returns the number of bytes written. On failure it returns a
negative error code.
Error Conditions:
See ip(L) for a description of the error codes.
TCPIP BADMODE
TCPIP PACKSIZE
STD INTR
ip ioc getconf
errstat
ip ioc getconf(chan cap, ipconf)
capability *chan cap;
struct nwio ipconf *ipconf;
Ip ioc getconf reports the Internet address and the netmask. For the nwio ipconf structure
see ip ioc setconf below.
ip ioc getopt
errstat
ip ioc getopt(chan cap, ipopt)
capability *chan cap;
struct nwio ipopt *ipopt;
This call returns in ipopt the current options of the IP channel specified by chan cap. See
ip ioc setopt for a description of the options.
ip ioc getroute
errstat
ip ioc getroute(chan cap, route)
capability *chan cap;
struct nwio route *route;
Ip ioc getroute can be used to query an IP server about it is routing table. The structure
nwio route is defined in server/ip/gen/route.h:
typedef struct nwio route
{
u32 t nwr ent no;
u32 t nwr ent count;
ipaddr t nwr dest;
ipaddr t nwr netmask;
ipaddr t nwr gateway;
u32 t nwr dist;
u32 t nwr flags;
u32 t nwr pref;
ipaddr t nwr ifaddr;
} nwio route t;
ip ioc setconf
errstat
ip ioc setconf(chan cap, ipconf)
capability *chan cap;
struct nwio ipconf *ipconf;
Ip ioc setconf can be used to inform the IP server about its Internet address and/or its
netmask. Normally an IP server will discover its Internet address using the RARP protocol.
Ip ioc setconf can be used in the case that the RARP failed, or the netmask has to be
changed. Note that higher level protocols (TCP and UDP) assume that the Internet address
of an IP device does not change, therefore TCP and UDP stop functioning if the Internet
address is changed.
The structure nwio ipconf is defined in server/ip/gen/ip io.h:
typedef struct nwio ipconf
{
u32 t nwic flags;
ipaddr t nwic ipaddr;
ipaddr t nwic netmask;
} nwio ipconf t;
ip ioc setopt
errstat
ip ioc setopt(chan cap, ipopt)
capability *chan cap;
struct nwio ipopt *ipopt;
Before an IP channel can be used, it has to be configured using ip ioc setopt . The structure
nwio ipopt is defined in server/ip/gen/ip io.h:
typedef struct nwio ipopt
{
u32 t nwio flags;
ipaddr t nwio rem;
ip hdropt t nwio hdropt;
u8 t nwio tos;
u8 t nwio ttl;
u8 t nwio df;
ipproto t nwio proto;
} nwio ipopt t;
ip ioc setroute
errstat
ip ioc setroute(chan cap, route)
capability *chan cap;
struct nwio route *route;
Ip ioc setroute adds a route to the routing table. See ip ioc getroute above for a description
of the nwio route structure. The fields nwr ent no and nwr ent count are ignored.
See Also
error(L), ip(L), ip eth(L), ip tcp(L), ip udp(L), ipsvr(A).
Name
ip tcp − Internet Protocol server’s TCP client interface stubs
Synopsis
#include "amoeba.h"
#include "server/ip/tcp io.h"
#include "server/ip/types.h"
#include "server/ip/gen/in.h"
#include "server/ip/gen/tcp.h"
#include "server/ip/gen/tcp io.h"
Description
The Internet Protocol (IP) server implements four network protocols, as described in
ipsvr(A). The routines described below give access to the ‘‘Transmission Control Protocol’’
(TCP) of the IP server. TCP implements a connection-oriented, byte-stream protocol on top
of IP (see RFC-793).
Access to this service is provided using two types of capabilities: a server capability and
channel capabilities. The server capability is called tcp. The server capability is used to
obtain a channel to the corresponding server. This is done with tcpip open (see ip(L)). The
channel capability can be used to transfer data using the protocol implemented by the server.
This is also done with the generic tcpip read and tcpip write routines. Since the semantics
of the tcpip functions vary slightly depending on channel type, these routines are described
briefly below. The tcp ioc routines are used to manage the options and status of the TCP
channel.
Before these routines are described, a brief introduction to the various types is given. The
general-purpose types and rights in the capabilities are described in ip(L).
TCP Types
server/ip/gen/tcp.h
defines tcpport t and Tcpport t for use in prototypes.
server/ip/gen/tcp io.h
defines struct nwio tcpconf (nwio tcpconf t), struct nwio tcpcl (nwio tcpcl t), struct
nwio tcpatt (nwio tcpatt t) and struct nwio tcpopt (nwio tcpopt t).
General Functions
tcpip read
errstat
tcpip read(chan cap, buffer, nbytes)
capability *chan cap;
char *buffer;
size t nbytes;
Tcpip read reads data from the TCP channel specified by chan cap into buffer . The call
blocks until either nbytes of data are available, or an error condition is detected. On success
it returns the number of bytes read. If the connection has been closed then it returns 0. If
urgent data is pending and the channel is not configured for urgent data then the error code
TCPIP URG is returned. The channel should then be reconfigured for urgent data using
tcp ioc setopt . Further tcpip reads should then be done until the error code
TCPIP NOURG is returned, indicating that there is no more urgent data. At this point the
channel can be reconfigured for normal operation.
Error Conditions:
See ip(L) for a description of the error codes.
STD INTR
TCPIP NOCONN
TCPIP URG
TCPIP NOURG
tcpip write
errstat
tcpip write(chan cap, buffer, nbytes)
capability *chan cap;
char *buffer;
size t nbytes;
Tcpip write writes the nbytes of data in buffer to the TCP channel specified by chan cap.
The call blocks until enough buffer space is available to hold the entire write. Urgent data
can be sent by setting the NWTO SND URG option using tcp ioc setopt and then doing the
write.
TCP Functions
This returns in tcpopt the current options for a TCP channel specified by chan cap. See
The behavior of a TCP channel can be changed using tcp ioc setopt . The parameters to
tcp ioc setopt are a channel capability and a struct nwio tcopt as defined in
server/ip/gen/tcp io.h:
typedef struct nwio tcpopt
{
u32 t nwto flags;
} nwio tcpopt t;
The nwto flags field takes the following flags:
#define NWTO NOFLAG 0x0000L
#define NWTO SND URG MASK 0x0001L
# define NWTO SND URG 0x00000001L
# define NWTO SND NOTURG 0x00010000L
#define NWTO RCV URG MASK 0x0002L
# define NWTO RCV URG 0x00000002L
# define NWTO RCV NOTURG 0x00020000L
#define NWTO BSD URG MASK 0x0004L
# define NWTO BSD URG 0x00000004L
# define NWTO NOTBSD URG 0x00040000L
#define NWTO DEL RST MASK 0x0008L
# define NWTO DEL RST 0x00000008L
# define NWTO NOTDEL RST 0x00080000L
NWTO SND URG and NWTO SND NOTURG control the sending of urgent data. If
NWTO SND URG is set, data written with tcpip write will be sent as urgent data. If
NWTO SND NOTURG is set, data will be sent as normal data. Similarly, NWTO RCV URG
and NWTO RCV NOTURG control the reception of urgent data. If the wrong kind of data is
present tcpip read returns the error TCPIP URG or TCPIP NOURG, as appropriate.
NWTO BSD URG and NWTO NOTBSD URG control whether data is sent and received
according to the BSD semantics or according to the semantics described in the RFC. These
options can only be specified if a successful tcp ioc connect or tcp ioc listen has been
done.
The NWTO DEL RST can be used to delay the sending of an RST packet in response to a
SYN packet. Normally, when a SYN arrives for a port and there is no listen for that particular
port, the TCP server will send an RST packet. This results in a ‘‘Connection Refused’’ error
at the sending host. If a server repeatedly accepts connections for the same port, it can use
this bit tell the TCP server not to send an RST immediately. When a SYN packet arrives and
the TCP server is about to send an RST packet, it will look for existing channels to the same
port with the bit set. If such a channel exists, the TCP server will delay the sending of the
RST packet to give the server an opportunity to open a new channel and start a listen.
Diagnostics
The complete set of error codes is described in ip(L).
See Also
error(L), ip(L), ip eth(L), ip ip(L), ip udp(L), ipsvr(A).
Name
ip udp − Internet Protocol server’s UDP client interface stubs
Synopsis
#include "amoeba.h"
#include "server/ip/udp io.h"
#include "server/ip/types.h"
#include "server/ip/gen/in.h"
#include "server/ip/gen/udp.h"
#include "server/ip/gen/udp io.h"
Description
These routines give access to the UDP protocol of the IP server (see ipsvr(A)). Access to
this service is provided using two types of capabilities: a server capability and channel
capabilities. The server capability is called udp. The server capability is used to obtain a
channel to the corresponding server. This is done with tcpip open (see ip(L)). The channel
capability can be used to transfer data using the protocol implemented by the server. This is
also done with the generic tcpip read and tcpip write routines. Since their semantics vary
slightly depending on channel type, these routines are described briefly below. There is also
a more convenient interface for reading and writing described in the UDP Library Functions
section below.
The udp routines are used to manage the options and status of the UDP channel.
Before they are described, a brief introduction to the various types is given. The general-
purpose types and rights in the capabilities are described in ip(L).
UDP Types
server/ip/gen/udp.h
defines udpport t and Udpport t for use in prototypes.
server/ip/gen/udp io.h
defines struct nwio udpopt (nwio udpopt t).
server/ip/gen/udp hdr.h
defines struct udp hdr (udp hdr t) and struct udp io hdr (udp io hdr t).
tcpip read
errstat
tcpip read(chan cap, buffer, nbytes)
capability *chan cap;
char *buffer;
size t nbytes;
Tcpip read reads UDP packets from the UDP channel specified by chan cap into buffer ,
which is of size nbytes . The call blocks until a complete packet is available. If successful it
returns the number of bytes read.
It is possible to obtain just the data from the UDP packets or the data plus UDP headers. See
udp ioc setopt below for details.
Error Conditions:
See ip(L) for a description of the error codes.
STD INTR
TCPIP BADMODE
tcpip write
errstat
tcpip write(chan cap, buffer, nbytes)
capability *chan cap;
char *buffer;
size t nbytes;
Tcpip write writes the UDP packet in buffer , of size nbytes to the UDP channel specified by
chan cap.
Error Conditions:
See ip(L) for a description of the error codes.
TCPIP BADMODE
udp connect
errstat
udp connect(udp cap, chan cap, srcport, dstport, dstaddr, flags)
capability *udp cap;
capability *chan cap;
udpport t srcport;
udpport t dstport;
ipaddr t dstaddr;
int flags;
Udp connect combines the functionality of tcpip open and udp ioc setopt. A pointer to a
UDP server capability should be passed in udp cap, and the channel capability will be
returned in the capability pointed to by chan cap. If srcport is 0 then an unused port will be
selected, otherwise the local port will be set to srcport. If dstport is non-zero then
communication will be restricted to remote ports equal to dstport, otherwise any data can be
sent to or received from any remote port. The same applies to dstaddr: if dstaddr is non-zero
then only dstaddr can be reached. Currently no flags are defined so flags should be 0.
udp reconnect
errstat
udp reconnect(chan cap, srcport, dstport, dstaddr, flags)
capability *chan cap;
udpport t srcport;
udpport t dstport;
ipaddr t dstaddr;
int flags;
Udp reconnect is the same as udp connect except that an existing channel capability is
(re)used.
udp close
errstat
udp close(chan cap, flags)
capability *chan cap;
int flags;
Udp close cleans up the administration kept by the UDP library but does not destroy the
capability. This function should be used if the capability was passed to another process and
should continue to exist. No flags are defined so flags should be 0.
udp destroy
errstat
udp destroy(chan cap, flags)
capability *chan cap;
int flags;
Udp destroy not only cleans up the administration kept by the UDP library but also destroys
the channel capability.
See Also
error(L), ip(L), ip eth(L), ip ip(L), ip tcp(L), ipsvr(A).
Name
libmod2 − introduction to the Modula 2 libraries
Description
This document describes the modules supplied with the ACK Modula-2 compiler.
ASCII.def
Contains mnemonics for ASCII control characters
Ajax.def
Currently only contains the function ‘‘isatty’’.
PROCEDURE isatty(fdes: INTEGER): INTEGER;
Arguments.def
This module provides access to program arguments and environment.
VAR Argc: CARDINAL;
Number of program arguments, including the program name, so it is at least 1.
PROCEDURE Argv( argnum : CARDINAL;
VAR argument : ARRAY OF CHAR
) : CARDINAL;
Stores the ‘‘argnum’th’’ argument in ‘‘argument’’, and returns its length, including a
terminating NULL-byte. If it returns 0, the argument was not present, and if it returns a
number larger than the size of ‘‘argument’’, ‘‘argument’’ was not large enough. Argument 0
contains the program name.
PROCEDURE GetEnv( name : ARRAY OF CHAR;
VAR value : ARRAY OF CHAR
) : CARDINAL;
Searches the environment list for a string of the form ‘‘name=value’’ and stores the value in
‘‘value’’, if such a string is present. It returns the length of the ‘‘value’’ part, including a
terminating NULL-byte. If it returns 0, such a string is not present, and if it returns a number
larger than the size of the ‘‘value’’, ‘‘value’’ was not large enough. The string in ‘‘name’’
must be NULL-terminated.
PROCEDURE Sort(
base: ADDRESS;(* address of array *)
nel: CARDINAL;(* number of elements in array *)
size: CARDINAL;(* size of each element *)
compar: CompareProc);(* the comparison procedure *)
PROCEDURE COBEGIN;
Beginning of a COBEGIN .. COEND structure.
PROCEDURE COEND;
End of a COBEGIN .. COEND structure.
PROCEDURE StartProcess(P: PROC);
Start an anonymous process that executes the procedure P.
PROCEDURE StopProcess;
Terminate a Process (itself).
PROCEDURE InitChannel(VAR ch: Channel);
Initialize the channel ch.
PROCEDURE GetChannel(ch: Channel);
Conversion.def
This module provides numeric-to-string conversions.
PROCEDURE ConvertOctal(num, len: CARDINAL;
VAR str: ARRAY OF CHAR);
Convert number ‘‘num’’ to right-justified octal representation of ‘‘len’’ positions, and put the
result in ‘‘str’’. If the result does not fit in ‘‘str’’, it is truncated on the right.
PROCEDURE ConvertHex(num, len: CARDINAL;
VAR str: ARRAY OF CHAR);
Convert a hexadecimal number to a string.
PROCEDURE ConvertCardinal(num, len: CARDINAL;
VAR str: ARRAY OF CHAR);
Convert a cardinal number to a string.
PROCEDURE ConvertInteger(num: INTEGER;
len: CARDINAL;
VAR str: ARRAY OF CHAR);
Convert an integer number to a string.
Epilogue.def
The procedure in this module installs module termination procedures to be called at program
termination. MODULA-2 offers a facility for the initialization of modules, but there is no
mechanism to have some code executed when the program finishes. This module is a feeble
attempt at solving this problem.
PROCEDURE CallAtEnd(p: PROC): BOOLEAN;
Add procedure ‘‘p’’ to the list of procedures that must be executed when the program
finishes. When the program finishes, these procedures are executed in the REVERSE order
in which they were added to the list. This procedure returns FALSE when there are too many
procedures to be called (the list has a fixed size).
InOut.def
This is Wirth’s Input/Output module.
MathLib0.def
This module provides some mathematical functions. See MathLib.def for a more extensive
one.
PROCEDURE sqrt(x : REAL) : REAL;
PROCEDURE exp(x : REAL) : REAL;
PROCEDURE ln(x : REAL) : REAL;
PROCEDURE sin(x : REAL) : REAL;
PROCEDURE cos(x : REAL) : REAL;
PROCEDURE arctan(x : REAL) : REAL;
PROCEDURE real(x : INTEGER) : REAL;
PROCEDURE entier(x : REAL) : INTEGER;
longpi = 3.141592*;
longtwicepi = 6.283185*;
longhalfpi = 1.570796*;
longquartpi = 0.785398*;
longe = 2.718281*;
longln2 = 0.693147*;
longln10 = 2.302585*;
Basic functions:
PROCEDURE pow(x: REAL; i: INTEGER): REAL;
PROCEDURE longpow(x: LONGREAL; i: INTEGER): LONGREAL;
PROCEDURE sqrt(x: REAL): REAL;
PROCEDURE longsqrt(x: LONGREAL): LONGREAL;
PROCEDURE exp(x: REAL): REAL;
PROCEDURE longexp(x: LONGREAL): LONGREAL;
PROCEDURE ln(x: REAL): REAL; (* natural log *)
PROCEDURE longln(x: LONGREAL): LONGREAL; (* natural log *)
PROCEDURE log(x: REAL): REAL; (* log with base 10 *)
PROCEDURE longlog(x: LONGREAL): LONGREAL;(* log with base 10 *)
Trigonometric functions (arguments in radians):
PROCEDURE sin(x: REAL): REAL;
PROCEDURE longsin(x: LONGREAL): LONGREAL;
PROCEDURE cos(x: REAL): REAL;
PROCEDURE longcos(x: LONGREAL): LONGREAL;
PROCEDURE tan(x: REAL): REAL;
PROCEDURE longtan(x: LONGREAL): LONGREAL;
PROCEDURE arcsin(x: REAL): REAL;
PROCEDURE longarcsin(x: LONGREAL): LONGREAL;
PROCEDURE arccos(x: REAL): REAL;
PROCEDURE longarccos(x: LONGREAL): LONGREAL;
PROCEDURE arctan(x: REAL): REAL;
PROCEDURE longarctan(x: LONGREAL): LONGREAL;
Hyperbolic functions:
PascalIO.def
This module provides for I/O that is essentially equivalent to the I/O provided by Pascal with
‘‘text’’, or ‘‘file of char’’. Output buffers are automatically flushed at program termination.
The CloseOutput routine is just there for compatibility with earlier versions of this module.
CONST Eos = 0C;
End of string character
TYPE Text;
VAR Input, Output: Text;
Standard input and standard output are available immediately. Standard output is not
buffered when connected to a terminal.
VAR Notext: Text;
Initialize your Text variables with this.
PROCEDURE Reset(VAR InputText: Text;
Filename: ARRAY OF CHAR);
When InputText indicates an open textfile, it is first flushed and closed. Then the file
indicated by ‘‘Filename’’ is opened for reading. If this fails, a run-time error results.
Otherwise, InputText is associated with the new input file.
PROCEDURE Rewrite(VAR OutputText: Text;
Filename: ARRAY OF CHAR);
When OutputText indicates an open textfile, it is first flushed and closed. Then the file
indicated by ‘‘Filename’’ is opened for writing. If this fails, a run-time error results.
Processes.def
This is the standard process module, as described by Wirth. As discussed in ‘‘Unfair Process
Scheduling in Modula-2’’, by D. Hemmendinger, SIGplan Notices Volume 23 nr 3, march
1988, the scheduler in this module is unfair, in that in some circumstances ready-to-run
processes never get a turn.
TYPE SIGNAL;
RealConver.def
This module provides string-to-real and real-to-string conversions.
PROCEDURE StringToReal(str: ARRAY OF CHAR;
VAR r: REAL;
VAR ok: BOOLEAN);
Convert string ‘‘str’’ to a real number ‘‘r’’ according to the syntax:
RealInOut.def
This is an InOut module for REAL numbers.
VAR Done: BOOLEAN;
Semaphores.def
This module provides semaphores. On systems using quasi-concurrency, the only
opportunities for process switches are calls to Down and Up.
TYPE Sema;
Storage.def
This module provides dynamic storage allocation. As Wirth’s 3rd edition mentions both
Allocate and ALLOCATE, both are included to avoid problems.
Streams.def
This module provides sequential IO through streams. A stream is either a text stream or a
binary stream, and is either in reading, writing or appending mode. By default, there are
three open text streams, connected to standard input, standard output, and standard error
respectively. These are text streams. When connected to a terminal, the standard output and
standard error streams are linebuffered. The user can create more streams with the
OpenStream call, and delete streams with the CloseStream call. Streams are automatically
closed at program termination.
Strings.def
This module provides string manipulations. Note: truncation of strings may occur if the user
does not provide large enough variables to contain the result of the operation.
Strings are of type ARRAY OF CHAR, and their length is the size of the array, unless a 0-
byte occurs in the string to indicate the end of it.
PROCEDURE Assign(source: ARRAY OF CHAR;
VAR dest: ARRAY OF CHAR);
Assign string source to dest.
PROCEDURE Insert(substr: ARRAY OF CHAR;
VAR str: ARRAY OF CHAR;
inx: CARDINAL);
Insert the string substr into str, starting at str[inx]. If inx is equal to or greater than
Length(str) then substr is appended to the end of str.
PROCEDURE Delete(VAR str: ARRAY OF CHAR;
inx, len: CARDINAL);
Delete len characters from str, starting at str[inx]. If inx >= Length(str) then nothing
happens. If there are not len characters to delete, characters to the end of the string are
deleted.
Termcap.def
This module provides an interface to termcap database. Use this like the C-version. In this
Modula-2 version, some of the buffers that are explicit in the C-version, are hidden. These
buffers are initialized by a call to Tgetent. The ‘‘ARRAY OF CHAR’’ parameters must be
NULL-terminated. You can call them with a constant string argument, as these are always
NULL-terminated in our Modula-2 implementation. Unlike the C version, this version takes
care of UP, BC, PC, and ospeed automatically. If Tgetent is not called by the user, it is called
by the module itself, using the TERM environment variable, or ‘‘dumb’’ if TERM does not
exist.
TYPE STRCAP;
PUTPROC = PROCEDURE(CHAR);
Traps.def
This module provides a facility for handling traps.
Constants:
ERRTOOLARGE (* stack size of process too large *)
ERRTOOMANY (* too many nested traps + handlers *)
ERRNORESULT (* no RETURN from function procedure *)
ERRCARDOVFL (* CARDINAL overflow *)
ERRFORLOOP (* value of FOR-loop control variable
* changed in loop
*)
ERRCARDUVFL (* CARDINAL underflow *)
ERRINTERNAL (* Internal error; should not happen *)
ERRUNIXSIG (* received Amoeba signal *)
Procedures:
TYPE TrapHandler = EM.TrapHandler;
XXTermcap.def
Like Termcap.def this module provides interface to termcap database, only this module
interfaces directly to C routines.
TYPE PUTPROC = PROCEDURE(CHAR);
VAR PC: CHAR;
UP, BC: ADDRESS;
ospeed: INTEGER[0..32767];
random.def
This module provides random numbers.
PROCEDURE Random(): CARDINAL;
Return a random CARDINAL.
PROCEDURE Uniform (lwb, upb: CARDINAL): CARDINAL;
Return CARDINALs, uniformly distributed between ‘‘lwb’’ and ‘‘upb’’. ‘‘lwb’’ must be
smaller than ‘‘upb’’, or ‘‘lwb’’ is returned.
PROCEDURE StartSeed(seed: CARDINAL);
Name
libpc − library of external routines for Pascal programs
Synopsis
const bufsize = ?;
type br1 = 1..bufsize;
br2 = 0..bufsize;
br3 = -1..bufsize;
ok = -1..0;
buf = packed array[br1] of char;
alfa = packed array[1..8] of char;
string = ˆpacked array[1..?] of char;
filetype = file of ?;
long = ?;
{all routines must be declared extern}
function argc:integer;
function argv(i:integer):string;
function environ(i:integer):string;
procedure argshift;
procedure buff(var f:filetype);
procedure nobuff(var f:filetype);
procedure notext(var f:text);
procedure diag(var f:text);
procedure pcreat(var f:text; s:string);
procedure popen(var f:text; s:string);
procedure pclose(var f:filetype);
procedure trap(err:integer);
procedure encaps(procedure p; procedure q(n:integer));
function perrno:integer;
function uread(fd:integer; var b:buf; len:br1):br3;
function uwrite(fd:integer; var b:buf; len:br1):br3;
function strbuf(var b:buf):string;
function strtobuf(s:string; var b:buf; len:br1):br2;
function strlen(s:string):integer;
function strfetch(s:string; i:integer):char;
procedure strstore(s:string; i:integer; c:char);
function clock:integer;
Examples
The following program presents an example of how these routines can be used. This
program is equivalent to the command cat(U).
begin {main}
if argc = 1 then
copy(input)
else
repeat
s := argv(1);
if (strlen(s) = 1) and (strfetch(s,1) = ’-’)
then copy(input)
else copy(inp);
argshift;
until argc <= 1;
end.
Another example gives some idea of the way to manage trap handling:
procedure traphandler(n:integer);
begin if n=EFOVFL then trapped:=true else trap(n) end;
procedure work;
var i,j:real;
begin trapped:=false; i:=1;
while not trapped do
begin j:=i; i:=i*2 end;
writeln(’bigreal = ’,j);
end;
begin
encaps(work,traphandler);
end.
Diagnostics
Two routines may cause fatal error messages to be generated. These are:
pcreat Rewrite error (trap 77) if the file cannot be created.
popen Reset error (trap 76) if the file cannot be opened for reading
Files
/profile/module/ack/lib/arch/tail pc: the library itself.
See Also
ack(U), pc(U).
Name
malloc − memory allocation package
Synopsis
#include "stdlib.h"
void * malloc(size)
void * calloc(num, elmsize)
void * realloc(ptr, newsize)
void free(ptr)
Description
This module provides a memory allocation package for user processes. It provides linear
performance. Malloc, calloc and realloc all return a pointer to memory guaranteed to be
sufficiently aligned for any hardware alignment restrictions on any data type. In most
implementations this is 8-byte alignment.
The package maintains a free list of any memory that it has available for use. If the package
has insufficient memory to satisfy a request it attempts to create a new segment with size
rounded up to the nearest click size larger than or equal to the amount of memory it needs to
satisfy the current request. Any unused memory in the segment is kept on a free list for
satisfying further requests for memory.
Note that writing to memory immediately before or after the piece allocated will corrupt the
memory administration and/or result in an exception. If a program crashes in the malloc
package then this is almost certainly happening (although see free below).
Functions
malloc
void *
malloc(size)
size t size;
Malloc allocates memory and returns a pointer to a piece of memory of size bytes. It returns
the NULL-pointer if it was unable to allocate sufficient memory.
calloc
void *
calloc(num, elmsize)
size t num;
size t elmsize;
Calloc allocates memory using malloc sufficient to hold num elements, each of elmsize
bytes and initializes the memory to zero. It returns the return value of malloc .
free
void
free(ptr)
void *ptr;
Free is used to return memory allocated by one of the above three routines to the free list so
that it can be reused in subsequent memory allocation requests. Ptr must be a pointer
returned by one of the above three routines. It is an error to free the same piece of memory
twice. This error will not be detected but it will probably result in an exception. Unlike with
some other memory allocation packages, once a piece of memory has been free d it is not
permitted to access it again, even before the next call to an allocation routine. When free is
called, the package attempts to merge free blocks in its administration.
Warnings
The kernel malloc package is very similar but does not have the segment boundary
restrictions. It can merge free memory blocks that span segment boundaries.
Synopsis
#include "monitor.h"
#define getreq
#define putrep
#define trans
Description
The monitoring package provides a way of collecting statistics from a server using a simple
instrumentation mechanism. It is possible to put in calls to the macro MON EVENT. Each
time the macro is called during the execution of the program a counter corresponding to that
‘‘event’’ is incremented. The event is specified with an arbitrary string. The string used
should be unique within the program for each distinguishable event to be monitored.
When monitor.h is included it redefines the RPC routines trans, getreq and putrep (see
rpc(L)) so that they automatically keep a count of the number of times each is called.
Furthermore, getreq has been modified to accept extra commands so that the statistics
gathered can be requested and manipulated by the program monitor(U).
The monitoring information is stored in a buffer allocated using malloc (L). If for some
reason it is not possible to use malloc in a program or a larger or smaller buffer is desired
than is used per default then before any calls to MON EVENT, trans, getreq or putrep a call
to MON SETBUF should be made with as arguments a buffer and the size of the buffer. The
buffer must be statically allocated (i.e., a global variable) or allocated with malloc .
Example
The following shows a simple server that uses monitoring. Many details are left out to avoid
obscuring the relevant part of the example. The include file mysvr.h contains the definitions
of CMD1 and CMD2.
server()
{
bufsize n;
header hdr;
for (;;) {
hdr.h port = getport;
n = getreq(&hdr, NILBUF, 0);
if (ERR STATUS(n)) {
MON EVENT("getreq failed");
continue;
}
switch (hdr.h command) {
case CMD1:
MON EVENT("Command one");
do command1(&hdr);
break;
case CMD2:
MON EVENT("Command two");
do command2(&hdr);
break;
default:
MON EVENT("Bad command");
break;
}
putrep(&hdr, NILBUF, 0);
}
}
See Also
malloc(L), monitor(U).
Name
msg − message-based RPC routines
Synopsis
#include "rrpc/msg rpc.h"
Description
The standard Amoeba communication primitives operate using user-defined buffers. The
msg routines provide an alternative based upon a message object. Messages are typed ; a
message consists of a sequence of values each tagged with its type. By using typed
messages, the recipient of a message need not know beforehand the types of the values being
sent to it. The use of types also provides a degree of protection against programming errors,
ensuring that the contents of a message are interpreted correctly.
The supported types are denoted by integer constants as shown in the following table:
Name Description
TYPE INT (32-bit) integer
TYPE REAL (float) floating-point
TYPE STRING NULL-terminated string
TYPE ADDRESS Amoeba port
Error codes
All routines with return type int return −1 upon failure.
msg newmsg
message *
msg newmsg()
A message is created by invoking msg newmsg . This routine returns a pointer to a message
structure; this pointer is used for all subsequent references to the message. Values are stored
in a message using the various msg put routines. If a new message could not be allocated
then this routine will not return but abort the calling program.
msg put
void
msg put(mp, type, val)
message *mp;
int type;
void *val;
Each invocation of a msg put routine appends the value to the end of the message.
Routines are provided for each of the four types currently supported. In addition, msg put
may be called with the type and a pointer to the value; this routine provides a type-generic
way of storing values in a message.
The NULL-terminated string pointed to by val is stored in the message pointed to by mp.
The Amoeba port value pointed to by val is stored in the message pointed to by mp.
msg rpc
int
msg rpc(addr, entry, req mp, reply)
port *addr;
int entry;
message *req mp;
message **reply;
A message-based RPC with a server is carried out by calling msg rpc, passing as parameters
the server’s put-port , the procedure entry number, and the parameters for the remote
procedure as encapsulated in a message. The final parameter to msg rpc is an out-parameter,
set upon successful return to point to a message containing the server’s response. If the RPC
fails for any reason then msg rpc returns −1.
The message received from the server is examined by using the various msg get routines.
The parameter val points to an object of sufficient size to hold a value of the indicated type .
The value of the indicated type is extracted from the message mp and stored in the object
pointed to by val.
Reads the next value from the message, which must be an integer, and stores the value in
object val.
Reads the next value from the message, which must be an float, and stores the value in object
val.
Copies the next value from the message, which must be an Amoeba port, and stores the value
in val.
Sets val to point to the character string at the current read position within the message msg.
The pointer is only valid for the lifetime of the message.
Msg next type may be called to determine the type of the next value; if no more values are
present in the message, then msg next type returns −1.
All the msg get routines likewise return −1 if the message mp has been exhausted. A type
mismatch occurs when the requested type and the type of the next value in the message
differ. This results in a fatal library error: the program exits after printing a descriptive error
message.
MSG ENTRY
int
MSG ENTRY(mp)
message *mp;
If the sender or creator of a message has called ctx define context (see msg rpc(L)), then
MSG SENDER NAME returns the name of the process which sent the message
If the sender or creator of a message has called ctx define context (see msg rpc(L)), then
msg sender port returns a pointer to the put-port of the process which sent the message.
Once the contents of a message are no longer needed, the space consumed by the message
should be freed by calling msg delete . Note that a client doing an RPC should invoke
msg delete on both its request message and the reply message returned by msg rpc. The
message library maintains with each message a reference counter, only freeing the space
consumed by a message when the counter drops to zero.
msg increfcount
int
msg increfcount(mp)
message *mp;
Increments the reference counter for message mp. If a message pointer variable is set to
point to some message, and all other references to the message are deleted, but it is desired
that the contents of the message remain, then the message reference counter should be
explicitly incremented by calling msg increfcount. In general, msg increfcount should be
called after each time some pointer is set to point to a given message, and msg delete should
be called each time the value of a variable pointing to the given message is changed, or
becomes undefined (for example, before leaving some program scope).
msg rewind
void
msg rewind(mp)
message *mp;
Since the msg get routines sequentially access the contents of a message, it may be desirable
to rewind a message to the beginning. This is accomplished by calling msg rewind.
Separate get and put pointers are maintained for each message; msg rewind resets the get
pointer to the beginning of the message.
To receive a remote procedure call, a program calls msg rpc rcv, passing as a parameter the
get-port . Msg rpc rcv returns the next message received on the specified port, blocking
until such a request is received. Note that the msg rpc rcv routine is a wrapper on top of
getreq .
The reply message is transmitted by calling msg rpc reply , passing as parameters the
corresponding request message and the reply message. Note that the msg rpc reply routine
is a wrapper on top of putrep.
msg printaccess
void
msg printaccess(mp)
message *mp;
The routine msg printaccess prints the contents of the message mp to stdout in a form
convenient for program debugging.
Warning
The maximum size of a message is limited to a fixed constant.
Some of the above functions may be implemented as macros.
Floating point values are only correctly passed between machines of the same architecture,
and the floating-point value must occupy no more than 32 bits.
The restrictions imposed by Amoeba on the use of getreq and putput routines apply to
msg rpc rcv and to msg rpc reply .
Examples
There are examples showing how these routines are used in the sources. They are in the
directory src/test/lib/amoeba.
See Also
msg grp(L), msg rpc(L).
Name
msg grp − message-based group management routines
Synopsis
#include "rrpc/msg rpc.h"
Description
These routines provide a message-oriented group communication structure, built on top of
the lower-level Amoeba group facilities (see grp(L)). Besides being based upon typed
messages, these group facilities differ from their lower-level grp(L) counterparts in a number
of key respects. This group facility provides an atomic group create/join; if multiple
processes concurrently attempt to create a group, only one instance of the group will be
created. The membership of a group is automatically maintained despite the failures of
individual members; group members do not call a reset procedure to reform the group after
partial failures. This facility also allows group messages to be received either via
asynchronous call-backs or through blocking reads.
Error codes
These routines do not return an error condition. They block until they are able to return
successfully.
To join a group, a process calls msg grp join. This routine atomically creates the group if it
does not already exist. Msg grp join takes as parameters the name of the group as a string,
an integer variable specifying any options, and parameters used for state transfer. The only
options currently supported are MSG GRP MANUAL RCV, and MSG GRP RESILIENT.
The chosen options are ANDed together. The option MSG GRP RESILIENT indicates that
the group is to be fully resilient to failures (see grp(L)). The option
MSG GRP MANUAL RCV will be described shortly. All processes seeking to join a given
group must call msg grp join with the same parameters.
When a process joins an existing group, the library is capable of atomically transferring state
from the existing members of the group to the new member. This automatic state transfer
feature is useful when constructing fault-tolerant applications using active replication, also
known as the state-machine approach. If all processes belonging to the group are identical
and deterministic, then all replicas will have the same state at the time the join is processed.
(This follows from the deterministic nature of the processes, the fact that the group join event
is totally ordered with respect to all other group communication, and the fact that Amoeba
threads are scheduled non-preemptively by default.) The library causes the state from the
existing members to be transferred to the new member.
To carry out a state transfer, the state is divided into a number of domains. The parameter
num domains specifies the number of domains to be used. The user-specified routine
grp state xfer func is called once for each domain, with the domain number being passed as
the parameter. This routine is expected to return a message pointer. The grp state rcv func
is called in the new member once for each domain, with two parameters: the domain number
and a pointer to the message which was generated by calling grp state xfer func in one of
the existing group members with that domain number as the parameter. If no state transfer is
required when joining a group, then num domains should be specified as zero. In that case,
the parameters grp state xfer func and grp state rcv func should be specified as null-
parameters.
The msg grp join routine also allows a call-back routine to be specified, to be called
whenever the membership of the group changes. The specified call-back routine will be
called with two parameters: the group pointer and the value given as the arg parameter to the
msg grp join call. If a call-back routine is specified, it will be called for the first time
before the msg grp join routine returns.
The msg grp join routine returns a pointer to a group structure; this handle is used
A process may explicitly depart from a group by calling msg grp leave . The failure of a
process automatically causes the group to be reformed without the failed member. Unlike the
lower level Amoeba group routines (see grp(L)), processes using these group routines do not
reform the group upon member failures or departures. The group routines described here
automatically maintain the membership of the group.
Note that a group join is totally ordered with respect to other group communication.
Moreover, just as each message sent is processed sequentially, a group join is not processed
until each message ordered before the join has been accepted by the program (either by
calling msg grp send or by returning from the appropriate call-back routine). It is not
possible that the state transfer routines will be called while the program is in the middle of
executing the message receive call-back routine.
A message is broadcast to the group by invoking msg grp send, passing as parameters the
group pointer, an entry number, and a pointer to the message to be sent to the group. How
messages are handled upon receipt depends on whether or not the option
MSG GRP MANUAL RCV was specified.
If MSG GRP MANUAL RCV is not specified, then a separate thread is created which listens
for messages. When a message is received which was sent to a specified entry point, then the
user-defined call-back routine for that entry point is invoked, with a pointer to the newly
received message as the parameter. The message is deleted by the library after the call-back
routine returns; if the message is to persist, then the call-back routine should call
msg increfcount (see msg(L)).
Messages may be manually received, by specifying the option MSG GRP MANUAL RCV
when joining the group. In this case, no thread is created to listen for messages. Instead,
messages are accepted by calling the function msg grp receive, passing as a parameter the
group pointer. This routine blocks until a a message is received. It then returns a pointer to
the message.
Warning
If all members of a group fail, and new processes then immediately join an identically-named
group, then the underlying Amoeba group structure gets confused, resulting in incorrect
behavior. This problem can only be avoided by waiting a few seconds for Amoeba to clean
up the kernel group information before recreating the group.
Examples
There are examples showing how these routines are used in the sources. They are in the
directory src/test/lib/amoeba.
Name
msg rpc − fault-tolerant message-based remote procedure call routines
Synopsis
#include "rrpc/msg rpc.h"
CTXT NOD *
ctx define context(context name, replicated, num state domains,
state xfer routine, state rcv routine);
void ctx define entry(entry, func, entry name);
CTXT NODE *ctx lookup context(context name);
int ctx rpc(ctxtp, entry, *req mp, **reply);
Description
These routines, together with the message library routines (see msg(L)) provide a high-level
mechanism for making remote procedure calls. An application using these routines may be
made fault-tolerant by following some simple rules in constructing the program, and then
simply running multiple replicas of each application component. In particular, the program
components must be constructed as state-machines . Programs must be completely
deterministic, with their outputs determined solely by the sequence of messages received.
Error codes
The ctx rpc routine returns −1 if the remote procedure call fails, which happens if there is a
total failure of the service.
A program wishing to make or receive remote procedure calls using these library routines
must first identify itself to the library. A program gives itself an identity by calling the
routine ctx define context . The context name parameter specifies the name by which the
program (or replicated set of programs) is to be known by. If only a single replica of the
program is to be run, then the replicated parameter should have the value of false (zero); this
enables certain performance optimizations to be applied. Otherwise, the value of replicated
should be true (one).
To receive server requests, a program declares to the library the server routines to be called
when requests are received. Server procedures are identified by their entry number; up to
sixteen (16) entry points may be defined, numbered from zero to fifteen. A server procedure
is declared by calling ctx define entry . The parameters are the entry number to be
associated with this routine, the address of the routine to call, and the name of the routine as a
string. Upon receipt of a message for this entry point, the library automatically calls the
specified routine, passing a pointer to the message as the parameter. The routine returns a
pointer to the message to be returned to the client.
To make a remote procedure call, a program must first lookup the name of the appropriate
service. A service is looked up by calling ctx lookup context , passing as a parameter the
name of the service as a string. Ctx lookup context returns a pointer to an internal data
structure; this pointer is used from then on to identify the service.
A remote procedure call is carried out by calling ctx rpc, which takes four parameters. The
first parameter is the pointer identifying the service, as obtained by calling
ctx lookup context . The second parameter is the entry number of the procedure and the third
parameter is a pointer to the message which is to be passed to that procedure. The fourth
parameter is a pointer to a message pointer. The ctx rpc routine sets this pointer to point to
the reply message.
The main thread of a server program will typically call thread exit (see thread(L)) after
executing the necessary initialization routines. The library will automatically invoke the
server routines as necessary.
Warning
Note that these routines employ the msg routines (see msg(L)).
Examples
There are examples showing how these routines are used in the sources. They are in the
directory src/test/lib/amoeba.
See Also
msg(L), msg grp(L), rpc(L).
Name
mutex − thread synchronization primitives
Synopsis
#include "amoeba.h"
#include "module/mutex.h"
void mu init(mu)
void mu lock(mu)
void mu unlock(mu)
int mu trylock(mu, maxdelay)
Description
These operations implement mutexes (also known as binary semaphores). The use of
mutexes is necessary to protect data structures that are accessed concurrently by multiple
threads.
A mutex has two states: unlocked and locked. The basic operations on a mutex are lock and
unlock. Locking an unlocked mutex changes its state to locked. Attempting to lock an
already locked mutex blocks the thread that attempts the locking until the mutex is unlocked
by another thread. If multiple threads are blocked, waiting for a locked mutex to become
unlocked, exactly one of those will proceed and get the lock when it is unlocked; the others
will remain blocked until the mutex is unlocked again. It is legal to unlock a mutex from a
different thread than the thread that locked it; this can be used to emulate a sleep−wakeup
mechanism.
Mutexes are fair: when two threads with the same priority are waiting for the same mutex,
the system guarantees that the thread that has spent the longest time in the queue will get the
lock first. In case the process has threads with different priorities, the thread that has the
highest priority will get the lock first.
Note that a signal can arrive during the execution of a critical section of code. To avoid
signals preempting a process during a critical section the sig mu functions have been
provided. If they successfully lock the mutex with sig mu lock or sig mu trylock then all
further signals are delayed until the process exits the critical section with a call to
sig mu unlock . When sig mu unlock is called then the signal handlers will be called for
any signals that arrived during the critical section. The semantics and parameters of the
sig mu versions are identical to the mu versions in all other respects and so only the mu
versions are described below.
Types
The mutex data type declared in amoeba.h is an opaque data type; its only use should be to
declare mutexes. All operations on mutexes must be done through the functions below.
Functions
mu init
void
mu init(mu)
mutex *mu;
A mutex must be initialized (in the unlocked state) before use. This can be done using this
function or by declaring it as a global or static variable (in bss). No error conditions are
detected. Calling mu init on an already initialized mutex in the locked state has an
undefined effect, but it will probably have unpleasant consequences.
mu lock
void
mu lock(mu)
mutex *mu;
This function implements the lock operation described above. No error conditions are
detected.
mu unlock
void
mu unlock(mu)
mutex *mu;
This function implements the unlock operation described above. An exception (EXC SYS)
occurs if the mutex was not locked by any thread.
mu trylock
int
mu trylock(mu, maxdelay)
mutex *mu;
interval maxdelay;
This is a variant of the lock operation that gives up when it has been blocked unsuccessfully
for maxdelay milliseconds, when the call is interrupted by a signal (see signals(L)), or when
the process is continued after a stun (see process (L)). If maxdelay is zero, it succeeds only if
Warnings
When using mu trylock , always beware of premature error returns caused by continuation
after a stun; this can happen to any program when run under a debugger.
The mutex operations available in libamunix.a have limited use. Since only a single thread
of control exists, when an operation would cause a thread to block forever, abort is called.
Example
The following code is idiomatic for a self-initializing module whose operations may be
called from multiple threads without explicit initialization. This is the rationale for the
requirement that statically initialized mutexes must be valid: imagine two threads calling
mod routine below at the same time; both threads will find that initialization is necessary
and call init, but only one of the calls to init will execute the initialization code. Once
initialized is set, future calls to mod routine will skip the call to init altogether.
static mutex mu;
static int initialized;
See Also
process(L), semaphore(L), signals(L).
Edsger W. Dijkstra, Cooperating Sequential Processes .
Name
name − name-based functions for manipulating directories
Synopsis
#include "module/name.h"
Description
This module provides a portable, name-oriented interface to directory servers. It has the
advantage of being independent of the particular directory server in use, but it does not make
available all the functionality of every directory server, e.g., SOAP. It is similar to the
module containing the same set of functions but with the prefix ‘‘dir ’’ instead of ‘‘name ’’.
The main difference is that the ‘‘dir ’’ functions take an extra argument, origin, which is the
capability for a directory relative to which the given path is to be interpreted.
In each function, the path should be a ‘‘path name’’ that specifies a directory or directory
entry. To these functions, a directory is simply a finite mapping from character-string names
to capabilities. Each (name, capability) pair in the mapping is a ‘‘directory entry’’. The
capability can be for any object, including another directory; this allows arbitrary directory
graphs (the graphs are not required to be trees). A capability can be entered in multiple
directories or several times (under different names) in the same directory, resulting in
multiple links to the same object. Note that some directory servers may have more complex
notions of a directory, but all that is necessary in order to access them from this module is
that they satisfy the above rules.
A path name is a string of printable characters. It consists of a sequence of 0 or more
‘‘components’’, separated by ‘‘/’’ characters. Each component is a string of printable
characters not containing ‘‘/’’. As a special case, the path name may begin with a ‘‘/’’, in
which case the first component starts with the next character of the path name. Examples:
a/silly/path, /profile/group/cosmo-33.
If the path name begins with a ‘‘/’’, it is an ‘‘absolute’’ path name, otherwise it is a
‘‘relative’’ path name.
The interpretation of relative path names is relative to the current working directory. (Each
Amoeba process has an independent capability for a current working directory, usually
inherited from its creator — see cwd set below.)
In detail, the interpretation of a path name relative to a directory d is as follows:
Errors
All functions return the error status of the operation. They all involve transactions and so in
addition to the errors described below, they may return any of the standard RPC error codes.
STD OK: the operation succeeded;
STD CAPBAD: an invalid capability was used;
STD DENIED: one of the capabilities encountered while parsing the path name did not
have sufficient access rights (for example, the directory in which the
capability is to be installed is unwritable);
STD NOTFOUND: one of the components encountered while parsing the path name does not
exist in the directory specified by the preceding components (the last
component need not exist for some of the functions, but the other
components must refer to existing directories).
Functions
name append
errstat
name append(path, object)
char *path;
capability *object;
Name append adds the object capability to the directory server under path, where it can
subsequently be retrieved by giving the same name to name lookup. The path up to, but not
including the last component of path must refer to an existing directory. This directory is
modified by adding an entry consisting of the last component of path and the object
capability. There must be no existing entry with the same name.
Error Conditions:
STD EXISTS: name already exists
name replace
errstat
name replace(path, object)
char *path;
capability *object;
Name replace replaces the current capability stored under path with the specified object
capability. The path must refer to an existing directory entry. This directory entry is
changed to refer to the specified object capability as an atomic action. The entry is not first
deleted then appended.
Required Rights:
SP MODRGT in the directory that is to be modified
name breakpath
char *
name breakpath(path, dir)
char *path;
capability *dir;
Name breakpath stores the capability for the directory allegedly containing the object
specified by path in dir, and returns the name under which the object is stored, or would be
stored if it existed. It is intended for locating the directory that must be modified to install an
object in the directory service under the given path. In detail, name breakpath does the
following:
If the path is a path name containing only one component, name breakpath stores the
capability for either the current working directory (if the path name is relative) or the user’s
root directory (if the path name is absolute) in dir, and returns the path (without any leading
‘‘/’’).
Otherwise, the path is parsed into two path names, the first consisting of all but the last
component and the second a relative path name consisting of just the last component.
Name breakpath stores the capability for the directory specified by the first in dir and
returns the second. The first path name must refer to an existing directory.
Error Conditions:
STD NOTFOUND:the directory does not exist
name create
errstat
name create(path)
char *path;
Name create creates a directory and stores its capability in a directory server under path. If
path is a relative path name, the new directory is created using the server containing the
current working directory, otherwise the new directory is created using the server containing
the user’s root directory. (Note that either of these might be different from the server that
contains the directory which is to be modified by adding the new directory.)
The path up to, but not including the last component of path must refer to an existing
directory. This directory is modified by adding an entry consisting of a new directory, named
by the last component of path. There must be no existing entry with the same name.
Required Rights:
SP MODRGT in the directory that is to be modified
Error Conditions:
STD EXISTS: name already exists in the directory server
name delete
errstat
name delete(path)
char *path;
Name delete deletes the entry path from the directory server. The object specified by the
capability associated with path in the directory entry is not destroyed. If desired, it should be
separately destroyed (see std destroy (L)).
Required Rights:
SP MODRGT in the directory that is to be modified
Examples
/* Change the name /home/abc to /home/xyz: */
capability mod dircap, object;
char *old name = "/home/abc";
char modify dir[NAME MAX+1];
char *entry name = name breakpath(old name, &mod dircap);
See Also
direct(L).
Name
newproc − create a child process without forking
Synopsis
int newproc(file, argv, envp, nfd, fdlist, sigignore)
int newprocp(file, argv, envp, nfd, fdlist, sigignore)
char *file;
char *argv[];
char *envp[];
int nfd;
int fdlist[];
long sigignore;
Description
These functions are extensions of the process creation primitives fork and exec provided by
posix(L); they are much faster but less general, and intended to optimize the common cases.
Newproc is intended to replace the common case of a fork call followed (in the child) by
some I/O redirection, signal fiddling, and an exec call. Newprocp does almost the same, but
searches its file argument in the environment variable PATH using the same algorithm as the
shell.
The arguments are:
file, argv, envp The first three arguments correspond to the arguments to execve.
nfd, fdlist These arguments specify I/O redirection. If nfd is negative, the child will
inherit all open files from the parent except those with the close-on-exec
flag on. Otherwise, for 0 <= i < nfd, file descriptor i in the child will be
dupped from file descriptor fdlist[i] in the parent, if that refers to a valid file
descriptor whose close-on-exec flag is off, and closed otherwise; file
descriptors nfd and higher in the child are closed.
sigignore The last argument is a signal mask: signals corresponding to one bits in the
mask will initially be ignored by the child, while the default action will be
used for the others. Remember that signal i corresponds to bit i-1, as
defined by the sigmask() macro in signal.h.
Return Value
The return value is the process ID of the new process. This is a child of the calling process,
indistinguishable from one created by fork and exec.
Diagnostics
If no process was started, errno is set to indicate the error and −1 is returned.
Example
The following program starts the program given by its first argument, passing the remaining
arguments on, and waits for the process to finish. Environment, file descriptors and signals
are left unchanged.
#include "stdio.h"
main(argc, argv)
int argc;
char **argv;
{
int pid, sts;
if (argc < 2)
exit(2); /* Bad usage */
pid = newprocp(argv[1], argv+1, (char **)0,
-1, (int *)0, -1L);
if (pid < 0) {
perror(argv[1]);
exit(1);
}
printf("Child pid: %d\n", pid);
if (waitpid(pid, &sts, 0) < 0) {
perror("waitpid");
exit(1);
}
printf("Child status: %d\n", sts);
exit(0);
}
See Also
exec file(L), posix(L).
Name
one way − encrypt a port
Synopsis
#include "amoeba.h"
one way(p, q)
port *p, *q;
Description
One way computes an encrypted version of p and returns the result in q. It keeps an internal
cache of encrypted ports for efficiency reasons.
See Also
priv2pub(L), posix(L), rpc(L).
Name
opencap − open a file given by capability
Synopsis
#include "fcntl.h"
Description
This function returns an open file descriptor for the file referenced by the ‘capability’
argument. The ‘flags’ argument is interpreted as for open() (see posix(L)), but only the flags
O RDONLY, O WRONLY and O RDWR are supported. Furthermore, bullet files may only be
opened for reading; other files (such as tty devices) may be opened for reading and/or
writing. Errors are the same as for open() , insofar as they are relevant to the supported flags.
Example
The following code fragment (without error handling) looks up a capability and opens it, then
copies some bytes from it to standard output:
capability cap;
int fd, n;
char buf[100];
See Also
posix(L).
Name
path − functions for processing path names and lists of path names
Synopsis
#include "module/path.h"
Description
This is a collection of functions for parsing and translating path names and colon-separated
directory lists such as the PATH environment variable. This module implements the
semantics of ‘‘.’’ and ‘‘..’’ as path components, by doing syntactic evaluation of such
components in the user program. The directory servers do not recognize the special meaning
of these names.
path first
char *
path first(dirlist, filename, path buff)
char *dirlist;
char *filename;
char *path buff;
Given a colon-separated directory list, a file name and a pointer to a character buffer,
path first sets path buf to the result of appending filename (separated by ‘‘/’’) to the first
directory in the pathlist. However, if the first directory is an empty string or the file name
begins with ‘‘/’’, the file name is just copied to the buffer. In any case, a pointer to the
portion of dirlist after the first directory (and terminating ‘‘:’’) is returned, if there are
remaining directories, so that the result can be used in loops that are to iterate over the
directories in the list. If there are no remaining directories, NULL is returned as a terminator
for such loops.
Path first also returns NULL on any call, if filename begins with ‘‘/’’ (since there is no
point to further calls). The user is responsible for ensuring that the buffer pointed to by
path buff is large enough.
path norm
int
path norm(cwd, path, buf, len)
char *cwd;
char *path;
char *buf;
int len;
Given an absolute or relative path name (path) and the name of the current working directory
(cwd), path norm builds, in the given character buffer (buf), a normal form for the path
name. This normal form has any multiple slashes mapped to one, has no trailing ‘‘/’’ and has
all occurrences of ‘‘.’’ and ‘‘..’’ expanded away. Also, if the path does not start with ‘‘/’’,
and the current working directory is non-NULL, it is prefixed to the path before calculating
the normal form. Thus the path is changed from relative to absolute, if and only if a non-
NULL working directory is given.
It returns −1 if a normalized path name is not obtainable, either because path is NULL, or
cwd is needed and is NULL. Note: ‘‘/..’’ is equivalent to ‘‘/’’, and paths ending in ‘‘/’’ are
accepted.
path capnorm
int
path capnorm(dircap p, path, buf, len)
capability **dircap p;
char *path;
char *buf;
int len;
Similar to path rnorm, except that it uses a capability pointer instead of the name of the
current working directory. If the capability is the same as the working directory capability,
the path is normalized as if path rnorm had been called with the absolute path name of the
working directory as first argument. Otherwise, the path is normalized using a NULL
working directory string, in which case the path must not have a leading ‘‘..’’.
The capability is specified as a pointer to a pointer, because it needs to be modified if a path
name relative to the working directory is normalized to an absolute path name. In this case,
the capability pointer is changed to point to the capability for the user’s root directory.
path cs norm
int
path cs norm(dircapset p, path, buf, len)
capset **dircapset p;
char *path;
char *buf;
int len;
Similar to path rnorm, except that it uses a pointer to a capability-set (dircapset p) instead
of the name of the current working directory. If any capability in the set is the same as the
working directory capability, the path is normalized as if path rnorm had been called with
the absolute path name of the working directory as first argument. Otherwise, the path is
normalized using a NULL working directory string, in which case the path must not have a
leading ‘‘..’’.
The capset is specified as a pointer to a pointer, because it needs to be modified if a path
name relative to the working directory is normalized to an absolute path name. In this case,
See Also
soap(L).
Name
pd preserve − copy a process image to a file
Synopsis
#include "module/proc.h"
errstat
pd preserve(filesvr, pd, pdlen, file)
capability *filesvr;
process d *pd;
int pdlen;
capability *file; /*out*/
Description
Pd preserve stores the process descriptor pd, which has length pdlen, and copies of the
segments on file server filesvr. It returns a capability for the new file in file. The resulting
file is in network byte order. In theory this file is executable again, but since the capability
environment and command line arguments are copied as well, the resulting program is not
very useful. It is used to create core dumps, which can be inspected by debuggers.
Diagnostics
In case of an error, pd preserve returns one of the STD or RPC errors defined in stderr.h . In
particular, STD NOMEM is returned when malloc fails, but unfortunately, the file server is
allowed to return this error when it ran out of memory as well.
Example
This function tries to store a process descriptor in a file called core.
#include "amoeba.h"
#include "module/proc.h"
#include "stderr.h"
#include "server/bullet/bullet.h"
void dumpcore(pd)
process d *pd;
{
capability filesvr, file;
if (name lookup(DEF BULLETSVR, &filesvr) == STD OK &&
pd preserve(&filesvr, pd, pd size(pd), &file) == STD OK)
(void) name append("core", &file);
}
Name
pd read − read a process descriptor from a file
Synopsis
#include "module/proc.h"
process d *
pd read(file)
capability *file;
errstat
pd read multiple(cap, pd array, npd)
capability *cap;
process d ***pd array;
int *npd;
Description
Pd read reads a process descriptor from a file using b read (see bullet (L)). The argument
specifies a capability for the file. It converts the data read from the file from standard byte
order to native byte order using pdmalloc and buf get pd (see process d(L)), and returns a
pointer to the malloced buffer containing the converted process descriptor.
Pd read multiple is used in heterogeneous process startup. It reads a set of process
descriptors, one per architecture available. The argument cap may be the capability for
either a file or a directory. If cap designates a file, pd read multiple behaves like pd read
and it will return a single process descriptor. If cap designates a directory, it should contain
one or more binary files named ‘‘pd.arch’’, each holding a process descriptor for
architecture arch, respectively. The process descriptors are returned in the output parameter
pd array by means of a dynamically allocated array of pointers to the process descriptors.
Its length is returned in the output parameter npd.
The segment descriptors in the process descriptors returned by pd read multiple are patched
to refer to the corresponding binary files. For backward compatibility reasons, the caller of
pd read is supposed to do this himself.
Diagnostics
Pd read returns a NULL-pointer if an error occurred; this includes errors reported by
b read, pdmalloc and buf get pd.
Pd read multiple returns STD OK if it could read and allocate at least one process
descriptor. Otherwise it returns a standard error code telling what went wrong.
Name
posix − the POSIX operating system interface support
Synopsis
Support for POSIX library functions and header files is discussed.
Description
The Amoeba library is evolving towards as much POSIX support as possible. Because most
of these functions are adequately documented by POSIX, only those functions where
Amoeba’s POSIX implementation differs from the standard are described. A companion
document, ansi C(L), provides information about functions not defined by POSIX but by the
C Standard. In cases where both standards have conflicting definitions, Amoeba follows
POSIX.
Many of the POSIX library functions use the services of the session server (see session (U))
to assist in their implementation. The session server provides shared file descriptors, process
management support and pipes (although named pipes or FIFOs are implemented by
fifosvr(A)).
Specific deviations from POSIX are listed below. It should also be noted that the library
contains many reserved names that are used by the POSIX emulation itself (e.g., the Amoeba
transaction primitives). They should not be overridden by applications.
For easy reference, the sections below are numbered the same way as the chapters in the
POSIX standard. All supported header files are found in the header subdirectory posix.
P.3.1.1
Directory stream positioning is not shared by parent and child after a fork.
P.3.1.2
If the environment variable PATH is not present, the functions execlp and execvp use
:/bin:/usr/bin as the search path.
When computing the size of the arguments to exec, the sum is taken over the arguments
including NULL-terminators, pointers and alignment bytes.
A regular file can be executed as a shell script when it begins with a line of the form
#! shell optional-arg (e.g., #! /bin/sh -x).
It is also possible to execute a directory containing a set of process descriptors (see
exec file(L)).
An additional primitive to start a child process without forking is provided. See
newproc(L).
P.3.3
The implementation defines the following additional signals: SIGAMOEBA, SIGSYS,
SIGTRAP and SIGBUS.
SIGCHLD is not supported.
If a subsequent occurrence of a pending signal is generated, the signal is delivered at
most once.
P.3.3.2
If the first parameter of kill is -1, all processes except the calling process are killed,
which is what BSD does.
P.3.3.4
An attempt to set the action for a signal that cannot be caught or ignored to SIG DFL
fails with errno set to [EINVAL].
P.3.4.3
sleep is not based on alarm . When a SIGALRM signal is scheduled and there is a
handler present, sleep will return after the handler is finished.
P.4.3.2
setsid is a dummy since the whole concept of a session is not supported.
P.4.3.3
No checks are made on the arguments of setpgid and thus no errors occur.
P.4.5.1
If the function time cannot reach the time-of-day server, it returns -1, clears the variable
pointed to by its argument if not a NULL-pointer, and sets errno to [EIO].
P.4.5.2
The function times returns a the number of milliseconds since system start-up. All
fields of the supplied buffer are set to zero.
P.5.1.2
The function readdir does not return the entries dot and dot-dot.
After a fork both parent and child can use a previously opened directory stream without
interfering each other.
P.5.2.2
If the buffer supplied to to getcwd is NULL, errno is set to [EINVAL] and NULL is
returned.
P.5.3.3
umask ignores bits other than file permission bits.
P.5.3.4
Links have totally different semantics. A link almost completely behaves as a copy of
the file, only cheaper. The exception is that the Amoeba std destroy request (see
std(L)) destroys a file, breaking all unmodified links to it.
Using link on arguments that name directories fails with errno set to [EPERM].
P.5.4.1
mkdir ignores bits other than file permission bits in its mode parameter.
P.5.4.2
mkfifo ignores bits other than file permission bits in its mode parameter.
P.5.5.1
Amoeba does not maintain file link counts; unlinked files stay around until garbage-
collected or explicitly destroyed with std destroy (see std(L)). When a file is updated
via the Amoeba POSIX emulation, any old version is automatically destroyed. Note
this also makes any link to the old version invalid. The reason for not relying fully on
garbage collection in this case, is that each update of a big log file would create an
unreachable copy, taking up lots of disk space. Garbage collection could be just too
slow when this happens regularly.
When the argument of unlink names a directory, the call fails with errno set to
P.5.5.2
rmdir is successful when path is a directory that is in use by another process or if it is
the current working directory of the calling process.
P.5.6.2
The functions stat and fstat return dummy data for certain members:
st mode Always reports read, write, modify permission for owner, group and others
(the actual permissions on a file are only revealed when it is opened).
st ino Set to a hash function of the capability’s private part (object number and
random check word).
st dev Set to a hash function of the capability’s port.
st nlink Set to one.
st uid, st gid
Set to one.
st atime, st mtime, st ctime
All three are set to the time when the entry was last placed in its directory.
The file times have an accurracy of about one minute.
Currently stat requires read permission for the file that is inspected.
Besides the required members, the stat structure contains the following members for
BSD (layout) compatibility. The members are not fully supported.
Environment Variables
If the POSIX libraries are compiled without NDEBUG defined and the variable AJAX DEBUG
is present in the string environment, a terse message will be printed when a POSIX library
function fails. Similarly, AJAX TRACE can trigger various other debugging messages.
These debugging messages are only intended when debugging the POSIX libraries and
should not be considered a permanent part of the interface.
See Also
IEEE Std 1003.1-1990 (POSIX)
ansi C(L), ctime(L), newproc(L), session(U), soap(A), std(L), tios(L).
Name
priv2pub − convert private port (get-port) to public port (put-port)
Synopsis
#include "amoeba.h"
void
priv2pub(priv, pub)
port *priv; /* in */
port *pub; /* out */
Description
The Amoeba publications describe how a one-way function is applied by the F-box to
convert a get-port to a put-port. This routine is used to implement the F-box in software.
The priv2pub function is a one-way function (in that it is extremely difficult to invert) which
is used to encrypt the port of a server.
Priv2pub converts a private port (also known as a get-port ) to a public port (also known as
put-port ). A private port is the port used by a server in its getreq call; a public port is the
port used by a client of that server in its trans call (see rpc(L)). When getreq is called the
kernel calls priv2pub to encrypt the private port and only accepts requests to the encrypted
port. Thus it is difficult for someone to pretend to be a server for which they do not know the
get-port . Note that when getreq returns the header argument will contain the put-port since
that is what the client sent.
It is legal for the priv and pub arguments to point to the same location. Neither should be a
NULL-pointer.
See Also
one way(L), rpc(L).
Name
process − kernel process server interface
Synopsis
#include "amoeba.h"
#include "cmdreg.h"
#include "fault.h" /* From src/h/machdep/arch/<architecture> */
#include "module/proc.h"
Description
The description of the process interface is split in several parts. The process descriptor data
structure and its basic access methods are described in process d(L). The low-level kernel
process server interface is described here. The high-level process execution interface is
described in exec file(L). See also pd read(L).
pro getdef
errstat
pro getdef(svr, log2ps ret, first ret, last ret)
capability *svr;
int *log2ps ret;
int *first ret;
int *last ret;
This returns three parameters of the memory management system of a process server. In
*log2ps ret it returns the logarithm base 2 of the page size (the number log2ps such that
1<<log2ps is the page size in bytes). In *first ret it returns the lowest page number usable
in the address space of a user program. In *last ret it returns one more than the highest page
number usable in the address space of a user program. These parameters are constants for a
particular process server instance. They are expressed in pages rather than bytes to avoid
overflow, since first<<log2ps or last<<log2ps may exceed the size of numbers that fit in a
long int.
pro getload
errstat
pro getload(svr, speed ret, load ret, mem ret)
capability *svr;
long *speed ret;
long *load ret;
long *mem ret;
This returns three numbers that give an indication of the current load on a process server. In
*speed ret it returns a rough indication of the CPU speed of the machine in instructions per
second. The number returned is an estimate made by the kernel at boot time for a particular
configuration; it should only be used to compare processors of the same architecture. In
*load ret it returns a load average over the past few seconds, computed from the average
number of runnable threads times 1024. This is a running average; more recent events have
more effect. In *mem ret it returns the available memory, in bytes. This does not
pro sgetload
errstat
pro sgetload(svr, speed ret, load ret, mem ret, exec cap)
capability *svr;
long *speed ret;
long *load ret;
long *mem ret;
capability * exec cap;
Pro sgetload does all the things pro getload does. In addition it also causes the kernel to
modify its process server’s capability. The new capability for the process server is returned
via exec cap. The process server accepts two capabilities at any one time: the old capability
and the new capability. The run(A) server uses this routine to keep changing the process
server’s capability so that malicious users do not gain permanent access to a host just because
they had access to it once.
pro swapproc
errstat
pro swapproc(svr, oldproc, newproc)
capability *svr;
capability *oldproc;
capability *newproc;
This routine does a transaction with the owner capability, svr, of a process. It sends the
capability of the original process which was owned and the capability of the new process
which has replaced it.
Process Interface
The following requests must be directed at a process capability as returned by pro exec. A
process can obtain its own process capability using getinfo (L).
The process capabilities of all processes running on a particular processor can be found by
looking up PROCESS LIST NAME (typically ‘‘ps’’) relative to its processor capability, and
pro setowner
errstat
pro setowner(proc, owner)
capability *proc;
capability *owner;
This changes the owner capability of the process to the capability pointed to by owner. If
owner is NULL or points to a capability with a NULL-port, the process has no owner from
now on.
pro getowner
errstat
pro getowner(proc, owner ret)
capability *proc;
capability *owner ret;
This returns the owner capability of the process in the variable owner. If the process has no
owner, owner is set to a capability with a NULL-port.
pro setcomment
errstat
pro setcomment(proc, comment)
capability *proc;
char *comment;
This sets the comment string of the process to the NULL-terminated string comment . The
string stored with the process is truncated to a system-dependent limit (typically 31). By
convention, the comment string contains the username of the user who started the process, a
colon, the command name of the executing program with leading path components stripped,
and the command line arguments, or as much of this as fits within the limit. A comment of
this form is set automatically when a process is started by exec file(L).
pro getcomment
errstat
pro getcomment(proc, buf, len)
capability *proc;
char *buf;
int len;
This returns the comment string of a process in the buffer buf, truncated to len−1 bytes and
terminated with a NULL-byte.
Example
Code to run a process with these primitives would be too big to give a working example here.
See the source code of exec file(L) for a working example.
See Also
exception(H), exec file(L), exec findhost(L), exitprocess(L), getinfo(L), mutex(L),
pd read(L), process d(L), run(A), sys newthread(L).
Name
process d − process descriptor, segment descriptor, thread descriptor
Synopsis
#include "amoeba.h"
#include "module/proc.h"
#include "fault.h" /* From src/h/machdep/arch/<architecture> */
int pd size(pd)
char *pd arch(pd)
Description
The description of the process interface is split in several parts. The process descriptor data
structure and its basic access methods are described here. The low-level kernel process
server interface is described in process (L). The high-level process execution interface is
described in exec file(L). See also pd read(L).
A process descriptor is the basic data structure used by the kernel and utilities that
manipulate processes. A process descriptor contains some fixed parts and two tables of
variable length: the segment descriptor list and the thread descriptor list.
typedef struct {
char pd magic[ARCHSIZE];
capability pd self;
capability pd owner;
uint16 pd nseg;
uint16 pd nthread;
} process d;
The process d data structure defines only the fixed parts of a process descriptor. Its
members are:
pd magic Specifies the architecture identifier describing the type of processor on which
the process runs or may run. The string is at most ARCHSIZE−1 bytes long
and NULL-padded to ARCHSIZE bytes, so it is always NULL-terminated.
pd self Ignored in executable files and by pro exec (see process (L)); in a checkpoint,
specifies the process capability of the process whose checkpoint it is. This is
included to simplify applications that own many processes.
pd owner Specifies the owner capability. A NULL-port means the process has no owner.
pd nseg Specifies the number of segment descriptors included in the process descriptor.
Must be at least one.
pd nthread Specifies the number of thread descriptors included in the process descriptor.
Must be at least one.
Segment Descriptors
typedef struct {
capability sd cap;
long sd offset;
long sd addr;
long sd len;
long sd type;
} segment d;
The segment list of a process descriptor is an array of segment descriptors . Each array entry
is a structure of type segment d. To access the segment descriptors, the macro PD SD must
be used. If p is a pointer to a process descriptor, then PD SD(p) is a pointer to its first
segment descriptor, and for 0 <= i < p->pd nseg, the expression PD SD(p)[i] is
its i-th segment descriptor (not a pointer).
In executable files and for pro exec, a segment descriptor describes a segment that must be
created to form part of the address space of the new process. In a checkpoint, a segment
descriptor describes an actually existing segment. Depending upon flags in sd type , some
segment descriptors do not describe a segment, and must be ignored. This is used to include
* So, since segments may not overlap, a loader must know an upper bound for the click size of the tar-
get kernel, or it may assign overlapping address ranges to segments. In practice, since we have to
work with existing loaders, this places an upper bound on the click size.
Thread Descriptors
typedef struct {
long td state;
long td len;
long td extra;
} thread d;
typedef struct {
long tdi pc;
long tdi sp;
} thread idle;
A thread descriptor is a variable-length object. Most of a thread descriptor is system-
dependent or architecture-dependent. The simplest form, sufficient to create a new thread,
only specifies an initial program counter and stack pointer. Thread descriptors describing
threads that have been running contain more information, such as the full register set and
some kernel state. Such thread descriptors may be passed to pro exec; this can be used to
migrate running processes.
The first thread descriptor is accessed through the PD TD macro: if p is a pointer to a process
descriptor, then the expression PD TD(p) points to its first thread descriptor. If t is a
pointer to a thread descriptor, then the expression TD NEXT(t) is a pointer to the next
thread descriptor; if t is the last thread descriptor, TD NEXT(t) points to the first byte after
the list of thread descriptors.
The thread d structure only describes the first few bytes. Its members are:
td state Contains kernel internal state flags for the thread. Set this to zero for a new
process.
td len Specifies the total length of this thread descriptor, in bytes, including the thread d
structure. For a new process, this should be set to
sizeof(struct thread d) + sizeof(struct thread idle)
td extra A bit vector specifying which other, system-dependent structures follow. The
Access Functions
pd size
int
pd size(pd)
process d *pd;
Computes the size of a process descriptor in bytes. This may be useful to allocate a buffer of
sufficient size to copy a process descriptor into. The size depends on the number of segment
descriptors and the number and size of thread descriptors.
pd arch
char *
pd arch(pd)
process d *pd;
Returns the architecture string of a process descriptor. This is a pointer to the pd magic
member; the function is provided only for backward compatibility.
buf put pd
char *
buf put pd(buf, bufend, pd)
char *buf;
char *bufend;
process d *pd;
buf get pd
char *
buf get pd(buf, bufend, pd)
char *buf;
char *bufend;
process d *pd;
Converts the buffer starting at buf and ending at bufend from standard byte order, copying it
into the process descriptor pointed at by pd (which is normally allocated by pdmalloc
below). It returns a pointer (pointing into the buffer) to the first byte after the consumed
input, or NULL if the conversion failed. Failure can be caused by insufficient or ill-
formatted data in the buffer.
pdmalloc
process d *
pdmalloc(buf, bufend)
char *buf;
char *bufend;
Returns a pointer to storage allocated with malloc (L), cast to (process d *). It inspects the
data in the buffer (which should be a process descriptor in standard byte order as created by
buf put pd) to estimate an upper bound for the number of bytes to allocate. It returns NULL
if the buffer contains ill-formatted data or if not enough memory was available. The caller
should free the allocated storage after using it, using free (see malloc (L)).
Warnings
The structures defined here may be changed or extended later. Only the name and meaning
of the members described here should be trusted; their exact type and relative position in the
structure are subject to change. This implies that it is not portable to use initializers or to
take the address of a member. Initializing a structure to zero and then filling in documented
members will be portable.
Examples
To walk through the list of segment descriptors of a process descriptor p:
See Also
bullet(L), exec file(L), malloc(L), pd read(L), process(L), segment(L).
Name
profiling − find performance bottlenecks in Amoeba C programs
Synopsis
void procentry(func)
void procexit(func)
void prof dump(fp)
Description
The profiling module provides a method of finding performance bottlenecks in a C program
that runs under Amoeba. It currently only works in conjunction with the ACK compiler (see
ack(U)). The program to be profiled should be compiled using the −p option of ack(U)
(which is linked to cc under Amoeba). The functions are not normally explicitly called by
the programmer. They are compiled in at the appropriate places by the −p option of the
compiler. The one exception is prof dump which may be called by the programmer as
described below.
There are two ways to obtain profiling information:
The first is to simply link the program with the standard version of libamoeba.a . To obtain
statistics from such a program it is necessary to add a call to the function prof dump just
before the program exits, or where the summary of statistics is desired. Note that no statistics
about calls to library routines will be reported in this case.
The second is to link the program with a specially compiled version of libamoeba.a , which
has been compiled with profiling on. When the program runs it collects information about
almost all the function calls made and when the program exits it prints a summary of all the
statistics. Calls to assembly code routines and a very small number of special C routines
(namely those used by procentry and procexit) are not reported in the statistics.
The profiling version of libamoeba.a can be made by adding the -DPROFILING flag to the
DEFINES in the Amakefile for libamoeba.a and then remaking the library. This causes the
entire library to be compiled with the −p option. System administrators may prefer to make a
separate directory next to lib/amoeba in the configuration tree, copy the Amakefile from
lib/amoeba to it and build the profiling version there.
If it is desired to profile libraries other than libamoeba.a then they should be compiled with
the −p flag.
procentry, procexit
void
procentry(func)
char * func;
void
procexit(func)
char * func;
A call to these function is added by the compiler at the start and end respectively, of each C
function call. They are given as argument the name of the function from which they are
called. The programmer can, of course, redefine these routines for other debugging purposes.
The version of these routines in libamoeba.a maintains, in core, statistics about the number
of times the function is called in each thread of the process and the time taken for each call.
A summary of this information can be printed using prof dump.
prof dump
void
prof dump(fp)
FILE * fp;
This function prints the statistics gathered by the procentry and procexit routines in
libamoeba.a . It prints them on the file referred to by fp. If fp is the NULL-pointer then it
prints on stderr .
If the profiling version of the library is used then this routine is called when the program
exits. If the standard version of libamoeba.a is used it is necessary to make explicit calls to
prof dump from within the program to obtain the statistics.
Profiling information is recorded and printed per thread. When printed it appears as follows:
------ thread 0 -------
clean up : 1 { 0, 0, 0 } [ list(1) ]
print result : 18 { 0, 8, 50 } [ list(18) ]
compare : 54 { 0, 0, 0 } [ list(54) ]
list : 1 { 650, 650, 650 } [ main(1) ]
main : 0 { 0, 0, 0 } [ ]
To provide a short example, the above information was taken from a program with a single
thread that was not linked with the profiling library. A fully profiled program would produce
much more information. The first column is the name of the function that was called. The
second column is the number of times that function was called. The next three numbers
between the braces are the minimum, mean and maximum execution times in milliseconds
taken by the function. The list between the brackets shows the names of the functions which
called the function in question and the number of times each of them called it.
See Also
ack(U).
Name
prv − manipulate capability private parts
Synopsis
#include "amoeba.h"
#include "module/prv.h"
Description
These functions are used by servers to make and validate capabilities and to extract the object
number from a capability.
Functions
prv decode
int
prv decode(prv, prights, random)
private *prv;
rights bits *prights;
port *random;
Prv decode is used to validate a capability when the original random number is known. It
operates on the private part of the capability. It checks the check field in prv and if the check
field is valid it returns in prights the rights in the capability. It returns 0 if it succeeded and
−1 if the check field was invalid. If prv decode fails then the capability was not valid and
should be rejected.
prv encode
int
prv encode(prv, obj, rights, random)
private *prv;
objnum obj;
rights bits rights;
port *random;
Prv encode builds a private part of a capability in prv using the object number obj, the rights
field rights and the check field pointed to by random. It returns 0 if it succeeded and −1 if
the rights or the obj argument exceeded the implementation limits.
Warnings
The current implementation restricts the object number to 24 bits and the rights field to 8 bits.
Example
The following function implements the std restrict operation:
errstat
impl std restrict(cap, mask, newcap)
capability *cap;
rights bits mask;
capability *newcap;
{
objnum obj;
rights bits rights;
struct foobar {
port random; /* Random checkword */
...
} *foo, *FindFoo();
/* Find object: */
obj = prv number(&cap->cap priv);
if ((foo = FindFoo(obj)) == NULL)
return STD CAPBAD; /* Never heard of */
/* Validate: */
if (prv decode(&cap->cap priv, &rights, &foo->random) != 0)
return STD CAPBAD; /* Do not trust cap */
/* Build new cap: */
rights &= mask;
newcap->cap port = cap->cap port;
if (prv encode(&newcap->cap priv, obj, rights, &foo->random) != 0)
return STD SYSERR; /* Should not happen */
return STD OK;
}
See Also
rpc(L).
Name
rawflip − routines to access FLIP protocol from user level programs
Synopsis
#include "amoeba.h"
#include "module/rawflip.h"
Description
These routines provide for access to the FLIP network protocol from user programs. The
calls are virtually identical to the ones as described in M.F. Kaashoek’s thesis, Group
Communication in Distributed Computer Systems and also to the calls used inside the
Amoeba kernel, to ease porting code between user mode and kernel mode.
The interface consists of the actual routines for sending and receiving messages plus some
miscellaneous support routines.
flip init
int
flip init(ident, receive, notdeliver)
long ident;
int (*receive)();
int (*notdeliver)();
Flip init initializes a process’ use of the FLIP protocol. Parameters are ident, a long passed
back during upcalls and the receive and notdeliver functions used for callback from kernel
space. To be able to do callback the flip init function does a thread newthread and all
upcalls are done from the forked thread. This might be relevant when accessing shared data
structures.
flip end
int
flip end(ifno)
int ifno;
Flip end terminates a process’ use of the FLIP protocol for the specified interface ifno. The
extra thread created during flip init will also terminate. Returns 0 on success, FLIP FAIL
on failure.
flip register
int
flip register(ifno, adr)
int ifno;
adr p adr;
Flip register registers the address pointed to by adr as an endpoint address for FLIP
communication. From then on any packet sent to the one-way-encrypted address is given to
this process using the receive function given to the flip init function. Registering the all zero
address indicates willingness to receive packets sent out by flip broadcast. Flip register
returns a small integer ep (endpoint) on success or FLIP FAIL on failure.
Note that repeated FLIP broadcasts with the same source address, message-id and offset
within a short space of time will not be sent. Furthermore, messages sent to different
incarnations of a process which reuses FLIP addresses may be discarded due to route caching
effects. Therefore it is important to use a new FLIP source address for each new invocation
of a process. The routine flip random (described below) is one way to do this.
It is unwise to use the same address both for unicast and multicast. Cacheing effects in the
kernel will make communication slower in that case. It is better to register an address for
unicast and to use as a source for all messages.
flip unregister
int
flip unregister(ifno, ep)
int ifno;
int ep;
Flip unregister stops FLIP listening to the FLIP address associated with the endpoint ep.
Due to the asynchronous nature of the underlying network layer there is a possibility that
some packets destined for that address are still underway and will be delivered through
receive. Returns 0 on success, FLIP FAIL on failure.
Flip broadcast broadcasts the packet pkt with length length using the source address
associated with ep. The distance the broadcast travels is limited by the hopcnt parameter.
Note that a hop count of 1 does not imply that the broadcast will go over Ethernet. For
example, the current implementation treats local communication and physical shared memory
as networks and a hop count of at least 3 is required to send something over Ethernet. The
actual value required should be determined experimentally. There is a maximum of 25 hops
in the current implementation. Returns 0 on successful sending of the message,
FLIP NOPACKET if no free packets were currently available and FLIP FAIL on failure.
A 0 return value does not imply successful delivery of the message.
flip unicast
int
flip unicast(ifno, pkt, flags, dst, ep, length, ltime)
int ifno;
char * pkt;
int flags;
adr p dst;
int ep;
f size t length;
interval ltime;
Flip unicast sends a unicast packet pkt with length length to the flip address dst using the
source address associated with ep. The locate timeout for finding the destination is set to
ltime milliseconds. Possible flags which can be bitwise ORed are FLIP INVAL to
invalidate any existing route cache entry, FLIP SYNC to wait for any necessary locate to
finish before returning from flip unicast , and FLIP SECURITY to only route over trusted
networks. If the destination is not found within the specified timeout, or if while sending the
packet a routing error occurs, the packet is returned through the notdeliver function specified
with flip init. Returns 0 on successful sending of the message, FLIP NOPACKET if no free
packets were currently available and FLIP FAIL on failure. A 0 return value does not
imply successful delivery of the message.
Flip multicast sends a multicast packet pkt with length length to the flip address dst using
the source address associated with ep. The packet will only be sent if at least n recipients are
found listening to this address. The locate timeout for finding the destination is set to ltime
milliseconds. Flags are as in flip unicast . If the destination is not found within the specified
timeout, or if while sending the packet a routing error occurs, the packet is returned through
the notdeliver function specified with flip init. Returns 0 on successful sending of the
message, FLIP NOPACKET if no free packets were currently available and FLIP FAIL on
failure. A 0 return value does not imply successful delivery of the message.
receive
int
receive(ident, pkt, dstaddr, srcaddr, message-id, offset, length,
total, flag)
long ident;
char *pkt;
adr p dstaddr, srcaddr;
f msgcnt t messid;
f size t offset, length, total;
int flag;
The receive function specified by the user in flip init is called when a packet arrives for one
of the addresses registered by calling flip register . The ident parameter is the same as
specified in flip init at the sender’s side. The message-id identifies the message. It is unique
per source address and per message. If a message arrives fragmented, all fragments will
contain the same message-id . The offset and length identify which part of the message
identified by message-id is contained in this packet. The total length of the message is total.
The user is responsible for reassembly of messages fragmented by the network layer. The
packet pkt points to data that is not available after receive returns, so must be copied to be
retained. Flag can contain FLIP UNTRUSTED if the packet crossed untrusted networks on
the way.
The notdeliver function specified by the user in flip init is called when a message sent by
this process is undeliverable. Parameters as in receive. Flag can also contain
FLIP NOTHERE if the packet was bounced back on the way to the destination because the
address was not known. This is the usual case for notdeliver .
Miscellaneous Routines
To be able to use the raw FLIP interface it is necessary to have a few support routines for
creating and encrypting FLIP addresses.
flip oneway
void
flip oneway(prv addr, pub addr)
adr p prv addr;
adr p pub addr;
Flip oneway encrypts the FLIP address pointed to by prv addr and returns the result in the
FLIP address pointed to by pub addr.
flip random
void
flip random(addr)
adr p addr;
Flip random generates a random FLIP address in the struct pointed to by addr. The space
for the address is set to 0.
Flip random reinit causes the next call to flip random to get a new seed for the random
number generator from the random number server. The random server is either the one
specified by the string environment variable RANDOM, if set, or otherwise from the default
Example
The following example skips huge amounts of detail but gives an indication of how things
work. Any messages that come in will be handled by the received function.
int ifno;
int ep;
addr t my address, addr receiver;
static void received(long ident, char *pkt, adr p dstaddr,
adr p srcaddr, f msgcnt t messid, f size t offset,
f size t length, f size t total, int flag)
{
/* fill in your receive protocol here */
}
static void notdelivered(long ident, char *pkt,
adr p dstaddr, f msgcnt t messid, f size t offset,
f size t length, f size t total, int flag);
{
/* fill in your ’not delivered’ protocol here */
}
ifno = flip init(1722, received, notdelivered);
if (ifno < 0) {
/* error ... *./
}
/* Create an address */
flip random(&my address);
/* Register an endpoint */
ep = flip register(ifno, &my address);
/* Send a point-to-point message. To get the address of the receiver
* you can broadcast it or perhaps get it as a command line argument.
*/
while (((res = flip unicast(ifno, message, 0, &addr receiver,
ep, buffer size, 10000)) == FLIP NOPACKET) && count < 5)
count++;
/* Clean up */
flip unregister(ifno, ep);
flip end(ifno);
Name
regex − BSD regex(3)/ed(U) compatible regular-expression routines
Synopsis
char *re comp(s)
int re exec(s)
int ignoreCase
int regex bopat[10];
int regex eopat[10];
Description
Public domain routines compatible with the BSD regex(3)/ed(U) compatible regular-
expression routines.
Functions
re comp
char *
re comp(regexp)
char *regexp;
Re comp compiles the regular expression in the string argument into an internal format that
is used by re exec to perform pattern matching. Re comp returns a NULL-pointer if the
conversion was successful. In all other cases re comp returns a pointer to a string containing
the relevant error message.
If re comp receives a NULL-pointer or an empty string as argument it will return a NULL-
pointer and leave the previously compiled regular expression intact. If there was no previous
regular expression re comp will return an error.
Re comp can compile the given regular expression into a case-insensitive regular expression.
To achieve this effect the programmer must set the value of the global variable ignoreCase,
defined inside the module containing re comp , to a non-zero value. This is a BSD
incompatible feature.
Re comp recognizes the following regular expressions:
[1] char matches itself, unless it is a special character (metachar): . \ [ ] * +
ˆ $
[2] . matches any character.
[3] \ matches the character following it, except when followed by a left or right
parenthesis, a digit 1 to 9 or a left or right angle bracket. It is used as an
escape character for all other meta-characters, and itself. When used in a set,
it is treated as an ordinary character.
re exec
char *
re exec(s)
char *s;
Re exec matches the regular expression compiled by re comp to the string s. Re exec
returns 1 for success, 0 for failure and -1 when the compiled regular expression is invalid.
When the match has been successful re exec allows access to the tags created by \( and \).
The values of the tags can be accessed through the arrays regex bopat and regex eopat that
contain offsets from the beginning of the string s. regex bopat[n] points to the first
character of the nth tag, regex eopat[n] points to the last. Tag 0 contains the string passed to
re exec. The regex bopat and regex eopat arrays are not a part of the original BSD
interface.
Name
rnd − random server interface
Synopsis
#include "module/rnd.h"
Description
This module is used for obtaining random numbers from a random number server (see
random(A)).
rnd setcap
void
rnd setcap(cap)
capability *cap;
Rnd setcap sets the random number server capability to be used by subsequent calls to
rnd getrandom to cap. It can be used to override the default random number server
capability. Note that it sets the random number server per process and not per thread.
rnd defcap
errstat
rnd defcap()
Rnd defcap selects the default random number server to be the random number server used
by subsequent calls to rnd getrandom. If the capability environment variable RANDOM is set
it uses that capability. Otherwise it uses the capability specified by DEF RNDSVR in the
include file ampolicy.h . This is typically /profile/cap/randomsvr/default. The value returned
by rnd defcap is the error status from the lookup of the default server.
Note that it is not necessary to call this routine before using rnd getrandom. If no call has
been made to rnd setcap then rnd getrandom will use the default server as defined by
rnd defcap .
Error Conditions:
RPC errors
STD OVERFLOW:size > MAX RANDOM
Environment Variables
RANDOM − capability for the random server.
Warnings
When rnd getrandom fails, it undoes the effect of any previous call to rnd setcap . That is,
the next rnd getrandom will call rnd defcap .
See Also
ansi C(L), uniqport(L).
Name
rpc − the interface stubs for remote procedure calls
Synopsis
#include "amoeba.h"
Description
The three calls getreq , putrep, and trans form the remote procedure call (RPC) interface for
interprocess communication. All point to point communication is, at the lowest accessible
level, structured along the client/server model; that is, a client thread sends a request message
to a service, one of the threads in one of the server processes gets the request, carries it out
and returns a reply message to the client. There is also the possibility for group
communication. This is described in grp(L). See also rawflip(L).
For historical reasons, a remote operation (a request followed by a reply) is called a message
transaction or just a transaction . A client thread invokes a transaction by a call to trans. A
server thread receives a request via a call to getreq and returns a reply by calling putrep.
Trans, getreq , and putrep are blocking; that is, a trans suspends a thread until the request is
sent, carried out and a reply is received; getreq suspends a thread until a request has been
received and putrep suspends a thread until the reply has been received by the appropriate
client thread’s kernel.
A request or reply message is described to the transaction system calls by means of three
parameters: a pointer to a header, a pointer to a buffer and the size of that buffer. The
header is a fixed-length data structure, containing addressing information, status information,
an operation code and some parameters. The buffer is an 8-bit-character array whose size
may vary from 0 to 30000decimal bytes. The parameters of getreq specify where in memory
the header and buffer of the request are to be received. The parameters of putrep specify the
reply to be sent and the parameters of trans specify the request to be sent and where in
memory the reply is to be received.
The following sections explain the port- and capability-based addressing mechanism used to
specify services and objects, the exact structure of request and reply messages, the three
system calls and the failure semantics of RPCs.
#define PORTSIZE 6
typedef struct {
int8 portbytes[PORTSIZE];
} port;
typedef struct {
int8 prv object[3];
uint8 prv rights;
port prv random;
} private;
typedef struct {
port cap port;
private cap priv;
} capability;
A server thread identifies itself to the system by giving its port in the header of the getreq
system call. The port that it gives must be the get-port , also called the private port, (see
priv2pub(L) for more details). A client identifies the object of its transaction − and with it
the service that manages that object − by giving the object’s capability in the port and
private fields of the request header. The port that the client has is the put-port and this is
also the port returned in the header of the getreq call when a message arrives. This is
illustrated in the example below.
Both client and server thus specify a port and this port is used by the system to bring client
and server together. To prevent other processes from impersonating a particular server, a
port has two appearances called get-port and put-port. A server needs to specify a get-port
when it does a getreq call and a client has to specify a put-port (as part of the capability)
when it makes a trans call. Get-port and put-port are related via a one-way function, F,
which transforms a get-port into a put-port. The function was chosen so that it is
‘impossible’ to compute a get-port from a put-port. The library function priv2pub does the
one-way function transformation. The system guarantees that a client’s request message
containing put-port P in its capability will only be delivered to a server thread which
specified get-port G in its getreq call, where P = F (G).
typedef struct {
port h port;/* 6 bytes */
port h signature;/* 6 bytes */
private h priv;/* 10 bytes */
command h command;/* 2 bytes */
int32 h offset;/* 4 bytes */
bufsize h size;/* 2 bytes */
uint16 h extra;/* 2 bytes, total 32 bytes */
} header;
Programmers are strongly encouraged to use the h size field to communicate the true length
of a message buffer to the receiver.
getreq
bufsize
getreq(hdr, buf, size)
header *hdr;
bufptr buf;
bufsize size;
A server thread uses getreq to receive a request from a client. After processing the request, it
must return a reply by a call on putrep, or forward the request to another server using
grp forward (see grp(L)). A server thread may carry out only one request at a time.
Successful getreq and putrep calls must therefore alternate. A server thread may always,
even while serving (that is, after getreq and before putrep), use trans calls to have operations
carried out by other servers.
A server thread must provide the get-port on which it receives in the h port field of the
header parameter. The code for receiving a request thus becomes:
header.h port = server get port;
status = getreq(&header, buf, size);
Getreq blocks a thread until a request is received (or until the thread is alerted by a signal).
The returned value, status, contains the number of bytes in the buffer when the call is
successful, or a negative error code. If the request attempted to pass more than size bytes, the
message placed in the area pointed to by buf is truncated to size. Errors are discussed in the
next section.
Upon successful completion, the header pointed to by hdr contains a copy of the header
given in the corresponding trans call of a client. Therefore the h port field will contain the
put-port which was used by the client. A subsequent call to getreq must be sure to
reinitialize h port to the get-port.
trans
bufsize
trans(request hdr, request buf, request size,
reply hdr, reply buf, reply size)
header *request hdr;
bufptr request buf;
bufsize request size;
header *reply hdr;
bufptr reply buf;
bufsize reply size;
Trans attempts to deliver a request to a server and passes back the server’s reply. Trans
blocks until the transaction has either failed or a reply is received. Trans takes six
parameters, three to specify the request to be sent to the server and three to specify the
memory locations to place the reply. The request header pointed to by request hdr must
contain the put-port of the service to be called in the h port field. The system guarantees not
to deliver a request to a server that has not specified the associated get-port in the h port
field of its getreq call. Note that there is only a negative guarantee. The system cannot
guarantee to deliver a request; it can merely make an effort. The system does guarantee,
however, that a request will be received at most once and by no more than one server. Trans
has at-most-once semantics.
When a trans fails, the contents of the reply header and reply buffer are undefined and may
have been changed. The contents of the request header and request buffer are guaranteed to
be unchanged, unless they overlap the reply header or reply buffer. It is common practice to
timeout
interval
timeout(length)
interval length;
Before a request can be delivered by the system, the client’s kernel must know where to send
the request. It must locate a server. Details of the locate mechanism are not relevant here,
but what is relevant is that there is a minimum time that the kernel will spend trying to locate
a suitable server. This per-thread locate timeout can be modified by the timeout system call.
Its argument length is the new minimum timeout in milliseconds. It returns the previous
value of the timeout. The default timeout is system dependent; it is typically 5 seconds. It is
not a good idea to set the timeout below 2 seconds (length = 2000) since it may result in
locate failures even though the server is actually available.
Signals
The previous section specified that when a trans is blocked and a signal is received while the
system is still trying to locate the server, trans will return the error code RPC ABORTED.
When the location of the server is known and the RPC has been sent to the server and no
reply has been received as yet, the reaction of the system to signals to be caught is altogether
different. In this case signals are propagated to the server. Thus the server will receive the
signal and can choose to react accordingly. It can, as per default, ignore the signal or catch it.
If the server ignores the signal the signal will have no effect in both server and client. If the
server handles the signal it can choose its own way of reacting, for example by returning an
error reply with putrep. If the server itself catches signals and is waiting for a reply to a
trans the signal will again be propagated.
Signals can abort calls to getreq , but can not abort calls to putrep. See also signals(L).
Example
The example below shows the typical server main loop for a very simple file server and a
sample of client code for calling that server for a read operation and a write operation. Its is
assumed that the client’s capability has the put-port associated with the server’s get-port.
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
See Also
error(L), grp(L), priv2pub(L), signals(L).
Name
sak − the Swiss Army Knife server interface stubs
Synopsis
#include "amoeba.h"
#include "caplist.h"
#include "server/sak/sak.h"
Description
The sak(A) server provides a mechanism to execute a transaction at a later date under various
conditions. These routines, along with the standard server stubs (see std(L)) provide the
interface to the sak server. The first two routines do not actually do transactions with the
server but package up the transaction that the server is to execute at a later date. The file
containing this package is then submitted using sak submit job. Sak list job is used to
examine the status of a job.
Access
Jobs are submitted using the server capability. No rights are required to submit a job. For
each job submitted a capability is returned. The capability must be touched regularly (see
std(L)) or it will be garbage collected after some time. The simplest way to ensure that jobs
are touched is to install the capability in the directory graph. (Cronsubmit(U) does this, for
example.)
Errors
In general, standard error codes are used but there is one error code specific to the sak server.
SAK TOOLATE is returned when an attempt is made to submit a job after the time specified
for the job to be executed.
Types
In the following, the variable sched specifies when the job is to be executed. Sched is an
array of length MAX SPEC. The entries are numbered,
MINU minutes (0 - 59)
HOUR hours (0 - 23)
MDAY day-of-month (1 - 31)
Functions
Error Conditions:
STD NOMEM: cannot malloc a buffer for the data,
Any of the errors of sak make transfile.
Any of the errors of name lookup(L).
Error Conditions:
STD COMBAD: attempt to list super capability
STD CAPBAD: no such job
STD DENIED: insufficient rights
STD NOMEM: the job info did not fit in the server’s internal buffer
Error Conditions:
Any of the errors returned by b create or b modify (see bullet(L)).
See Also
bullet(L), sak(A).
Name
seg map − routines to control process virtual memory
Synopsis
#include "amoeba.h"
#include "module/proc.h"
Description
These routines enable a user program to control its virtual address space. Seg map creates a
new segment, optionally filling it from a file or another segment, and maps the new segment
in. Seg grow grows or shrinks a segment. Seg unmap unmaps a segment.
These calls are implemented with system calls, so no rights are involved, and a process can
only manipulate its own address space in this way.
Functions
seg map
segid
seg map(cap, addr, len, flags)
capability *cap;
vir bytes addr;
vir bytes len;
long flags;
Seg map creates a new segment of length len and maps it into the process’ address space at
address addr. Addr should be a multiple of ‘‘clicks’’. The size of a click is architecture
dependent, but usually the same as the hardware page size. Also, architectures may place
restrictions on the range of virtual addresses that may be used by user programs. The routine
findhole(L) may be used to obtain an address where a segment may be mapped in and that
satisfies all the necessary conditions.
If cap is non-NULL and not a NULL-capability the segment is initialized from the specified
file or segment (using b read calls, see bullet (L)). The flags specify how the segment
should be mapped, whether it is growable, etc. A complete description of the bits in flags
can be found in process d(L).
On success, seg map returns a segment identifier, a small integer that can be passed to
seg grow or seg unmap.
seg unmap
errstat
seg unmap(id, cap)
segid id;
capability *cap;
This call unmaps segment id. The segment id is either the value returned by a previous call
to seg map, or the index of the segment in the segment descriptor array returned by
getinfo (L). If cap is not a NULL-pointer, a capability for the now unmapped segment is
stored here. This segment remains attached to the program, and will be deleted when the
program exits. If a NULL-pointer is specified for cap, the segment will be deleted
immediately.
Error Conditions:
STD COMBAD The segment id does not refer to a currently
mapped-in segment.
seg grow
errstat
seg grow(id, newsize)
segid id;
vir bytes newsize;
This call changes the size of a mapped segment. Depending on the growth direction
specified in the call to seg map, the segment will be grown (or shrunk) at the high end or the
low end.
Warnings
Due to problems in the current kernel it is often not possible to grow segments, even if
enough physical memory is available. For this reason modules that do memory allocation
(like malloc (L)) should always be prepared to allocate a completely new segment if it is
impossible to grow a segment.
See Also
bullet(L), getinfo(L), process(L), process d(L).
Name
segment − stubs to manage in-core segments
Synopsis
#include "amoeba.h"
#include "module/proc.h"
Description
These stubs allow programmers to control in-core segments managed by Amoeba process
servers. Ps segcreate creates a segment, and ps segwrite modifies it. Besides these calls
the Bullet Server calls b read and b size (see bullet (L)) and all applicable standard calls
(see std(L)) can also be applied to segments.
Rights bits
Of the rights used by the process server only three are applicable to segments:
PSR READ permission to read the segment (using b read).
PSR WRITE permission to modify the segment.
PSR DELETE permission the delete the segment (using std destroy ).
Functions
ps segcreate
errstat
ps segcreate(server, size, orig, newcap)
capability *server;
long size;
capability *orig;
capability *newcap;
This routine asks the process server server to create a segment of length size bytes. The
capability for the new segment is returned in newcap. If orig is not a NULL-pointer or a
NULL-capability the new segment will be initialized from the file (or segment) orig, using
b read calls. Otherwise, the segment will be zero-filled.
Required Rights:
NONE
Diagnostics
The routines return only standard error codes from stderr.h .
Warnings
Under some circumstances all rights (instead of only PSR READ as expected) for the orig
segment are required by the ps segcreate call, and the same is true for the ‘‘initial contents’’
segment in the seg map call (see seg map(L)). For this reason it is currently not a good idea
to strip rights from segment capabilities.
See Also
bullet(L), process(A), process(L), process d(L), seg map(L), std(L).
Name
semaphore − thread synchronization using counting semaphores
Synopsis
#include "amoeba.h"
#include "semaphore.h"
Description
These operations implement counting semaphores. What follows is an intuitive explanation
of semaphores, not a formal definition:
A semaphore contains a non-negative integer variable, usually called its level. The two
important operations on semaphores are up and down, which increment and decrement the
level, respectively. However, when a call to down would decrement the level below zero, it
blocks until a call to up is made (by another thread) that increments the level above zero.
This is done in such a way that the level will never actually go negative. You could also say
that the total number of completed down calls for a particular semaphore will never exceed
the total number of up calls (not necessarily completed), plus its initial level.
Types
The semaphore data type declared in semaphore.h is an opaque data type; its only use should
be to declare semaphores. All operations on semaphores must be done through the functions
below.
Errors
The semaphore package makes no consistency checks; illegal calls cause undefined behavior.
The source may be compiled with a debugging flag which adds some consistency checks; if
an illegal situation is detected a message is printed and the program is aborted.
sema init
void
sema init(psem, level)
semaphore *psem;
int level;
A semaphore must be initialized to a certain level by calling this function. This call is com-
pulsory; semaphores initialized to all zeros have an illegal state. The initial level must not be
negative.
sema level
int
sema level(psem)
semaphore *psem;
This function returns the semaphore’s current level. Since this value may be invalid the next
microsecond (if another thread changes the level), it is not useful for synchronization. It is
provided in the interface to allow printing the value of a semaphore for debugging purposes.
sema up
void
sema up(psem)
semaphore *psem;
This function implements the up operation described above.
sema down
void
sema down(psem)
semaphore *psem;
This function implements the down operation described above.
sema trydown
int
sema trydown(psem, maxdelay)
semaphore *psem;
interval maxdelay;
This is a variant of the down operation that gives up when it has been blocked unsuccessfully
for maxdelay milliseconds, or when the call is interrupted by a signal catcher (see
signals(L)), or when the process is continued after a stun (see process (L)). If maxdelay is
sema mup
void
sema mup(psem, count)
semaphore *psem;
int count;
This function is equivalent to count calls to sema up, but more efficient.
sema mdown
int
sema mdown(psem, count)
semaphore *psem;
int count;
This function is not quite equivalent to count calls to sema down! When count <= 0, it is a
no-op; otherwise, it is equivalent to between 1 and count calls to sema down. It returns the
actual number subtracted from the semaphore’s level. This number is determined as follows.
If, on entry into sema mdown, the semaphore’s level is > 0, the call proceeds immediately,
otherwise it blocks until the level is raised above zero. In all cases, the return value is
MIN(count, level). (Informally, you can think of all this as: sema mdown tries to do as many
calls as possible to sema down with as little effort as possible.)
sema trymdown
int
sema trymdown(psem, count, maxdelay)
semaphore *psem;
int count;
interval maxdelay;
This call is a combination of the functionality of sema trydown and sema mdown. If it
decides to block, it will block at most maxdelay milliseconds, or indefinitely but interruptible
if maxdelay is < 0. If the level is still zero at the end of the delay period, the semaphore is
not updated and the return value is negative; otherwise the effect and return value are the
same as for sema mdown.
Warning
Overflow of the level field is not detected.
init()
{
/* Buffer starts empty */
sema init(&slots filled, 0);
sema init(&slots empty, N SLOTS);
}
producer()
{
int next slot = 0;
for (;;) {
sema down(&slots empty);
"produce an item into buffer[next slot]"
next slot = (next slot + 1) % N SLOTS;
sema up(&slots filled);
}
}
consumer()
{
int next slot = 0;
for (;;) {
sema down(&slots filled);
"consume an item from buffer[next slot]"
next slot = (next slot + 1) % N SLOTS;
sema up(&slots empty);
}
}
Name
signals − signal and exception handling
Synopsis
#include "amoeba.h"
#include "exception.h"
#include "fault.h" /* From src/h/machdep/arch/<architecture> */
#include "module/signals.h"
/* Low-level interface: */
sys setvec(v, n)
sys sigret(us)
Description
Signals are used for asynchronous communication between threads within a process. A
thread may specify a catcher function for a particular signal number ; another thread may
asynchronously cause a call to this catcher in the catching thread by generating a signal. The
signal catching mechanism is also used to catch exceptions and client-to-server signals; more
about those later.
Do not confuse signals with stuns, which are used for asynchronous communication between
different processes (see process (L)). Signals are also used to emulate the C/ UNIX signal
mechanism (see ansi C(L) and posix(L)).
When a signal is generated, it is broadcast to all threads in the same process that have a
catcher for it. This may include the thread that generated the signal. Threads that have no
catcher for the signal are completely unaffected. In threads that catch the signal, system calls
are completed or aborted (see the next paragraph); the catcher is called just before the system
Type
The type
signum
is used to specify signal numbers. This is an integral type; it is defined in exception.h (see
exception(H)). Ordinary signals are identified by numbers larger than one. Signal number
one (SIG TRANS) identifies the client-to-server signal. Negative numbers identify
High-level Functions
sig uniq
signum
sig uniq()
Each call returns a unique signal number in the range [2ˆ16 .. 2ˆ31). It should be called no
more than 2147418112 times in one program.
sig raise
void
sig raise(sig)
signum sig;
This broadcasts the signal number sig to all threads in the same process that have catchers for
it. The signal number should be larger than one.
sig catch
errstat
sig catch(sig, catcher, extra)
signum sig;
void (*catcher)();
void *extra;
This specifies that signal sig must be caught in the current thread by the function catcher
with extra argument extra . If a NULL-pointer is passed as catcher, the signal will be
ignored.
The catcher should be declared by the user as follows (the function name and parameter
names may be freely chosen):
void
my catcher(sig, us, extra)
signum sig;
thread ustate *us;
void *extra;
where sig is the signal number, us is the pointer to machine dependent user state at the time
of the call (only useful for code that attempts to repair exceptions, and not available to calls
postponed by the blocking mechanism), and extra is the extra argument passed to the
corresponding sig catch call. The catcher is called in the catching thread. If and when it
returns, the thread continues at the point where it was interrupted. System calls may be
aborted as described earlier.
sig block
void
sig block()
This blocks calls to signal catchers in the current thread until a matching call to sig unblock
is made. Pairs of calls to sig block and sig unblock may be nested. They can be seen as
brackets around critical sections; only the last (outermost) call to sig unblock will unblock
signals. Blocking signals only delays the call to the catcher; the other effects of catching a
signal (aborting system calls, sending client-to-server signals) still happen immediately when
the signal is generated. Exception catchers are unaffected by the blocking mechanism.
sig unblock
void
sig unblock()
This unblocks calls to signal catchers in the current thread, when not nested between another
pair of calls to sig block and sig unblock. Any catcher calls pending for the current thread
are performed at this point. Catchers called through sig unblock get a NULL-pointer passed
for their frame pointer argument, since the frame will probably be invalid by the time the call
is made.
Low-level Functions
The following structure type is defined in exception.h:
typedef struct {
signum sv type;
long sv pc;
long sv arg3;
long sv arg4;
} sig vector;
It is used for the vector argument to sys setvec.
sys setvec
sys setvec(v, n)
sig vector *v;
int n;
This is the kernel interface used by sig catch . The argument v is a vector with n entries of
type sig vector. When a signal is generated, the kernel searches the vector for a catcher,
sys sigret
sys sigret(us)
thread ustate *us;
This function must be called to return from a system catcher; the stack frame of a system
signal catcher looks peculiar so an ordinary return statement would crash the program. The
argument must be the us argument passed to the system catcher.
Diagnostics
Possible return values from sig catch are:
STD OK all went well.
STD NOMEM no memory was available to update the signal administration.
STD ARGBAD an invalid signal number was given (e.g., zero).
Warnings
Resist the temptation to put calls to printf (see ansi C(L)) in a catcher function, even for
debugging. When such a catcher is called during another printf call (or any other stdio call),
the stdio administration may be damaged and output garbled or further printing made
impossible. It is safe to call write however (see posix(L)).
A thread which never makes a blocking system call cannot be interrupted by a signal when
non-preemptive scheduling is used by the process. This happens because in that case no
Example
The following program sleeps two seconds and then prints the string catcher called :
int flag = 0;
/*ARGSUSED*/
void catcher(sig, us, extra)
signum sig;
thread ustate *us;
void *extra;
{
flag = 1;
}
void async()
{
sleep(2);
sig raise(1234);
}
main()
{
sig catch(1234, catcher, (void *) 0);
thread newthread(async, 8000, (char *) 0, 0);
sleep(5);
if (flag) printf("catcher called\n");
exit(0);
}
See Also
ax(U), exception(H), mutex(L), posix(L), process(L), rpc(L), session(U), ansi C(L),
thread(L).
Name
soap − the Soap Server client interface stubs
Synopsis
#include "capset.h"
#include "server/soap/soap.h"
Description
This manual page describes the Soap Server stub routines. Along with the standard server
stubs (see std(L)) the Soap Server stubs provide the interface to the Soap Server. In addition
to the stubs there are several derived and related functions to provide a simple interface for
common operations. These are described in sp dir(L), sp mkdir (L) and sp mask (L). For
details about the server see soap(A).
Types
The type SP DIR is defined in the include file sp dir.h. It is analogous to the type DIR used
for accessing directories.
Another important type is capset , which is used to store a set of capabilities, possibly the
empty set. The type capset is defined in capset.h .
The type sp entry is defined in soap.h. It is used for storing or retrieving one or more
(name, capset) pairs in a single operation.
The type sp seqno t is used to represent the 64-bit directory sequence numbers, which are
supported by some directory servers. It is defined in soap.h.
Time
Soap stores a last updated time-stamp with each directory entry. It uses a precision of
seconds. This time-stamp cannot be relied upon to be accurate since it is derived from a
Time of Day server (see tod(A)), but not necessarily always the same one. In general it will
be moderately accurate but if one TOD server goes down and another is available it will be
used. Therefore time-stamps may not be monotonically increasing. This causes programs
such as make (U) to be even more unreliable than under UNIX and it is better to create
programs that do not depend on a consistent view of the time of day.
Errors
All of the stub functions return an error status as the value of the function. Soap returns all
the standard error codes (as defined in stderr.h ) plus the following:
SP UNAVAIL This is returned if the Soap Server is not currently accessible. (It is usually
equivalent to RPC NOTFOUND).
SP UNREACH This is returned if during the lookup of a capability-set, the Soap Server
encounters a capability for a directory within the same Soap Server, that
does not exist. This can happen if someone deletes a directory to which
multiple links exists. Note that when the object number of a deleted
Path names
Path names are always specified as a string relative to a directory whose capability-set is
known. To perform operations relative to the current working directory (whatever that might
be) the dir parameter in the library routines that take path name arguments can be specified
as SP DEFAULT.
Functions
sp append
errstat
sp append(dir, name, cs, ncols, cols)
capset *dir;
char *name;
capset *cs;
int ncols;
rights bits cols[];
Sp append adds the directory entry specified by name , relative to the directory specified by
dir. The entry may not exist already. The new entry has the capability-set cs.
The column masks for the directory entry are specified by the ncols entries in the array cols.
To avoid first having to look up the number of columns the directory has, any legal number
of columns masks (1 up to SP MAXCOLUMNS) is accepted. Masks referring to non-existent
columns are ignored, and missing masks are set to zero.
Required Rights:
SP MODRGT
Access to at least one column of the directory to which the new entry is
being added.
sp chmod
errstat
sp chmod(dir, name, ncols, cols)
capset *dir;
char *name;
int ncols;
rights bits cols[];
Sp chmod allows one to alter the column masks for the directory entry specified by the path
name name relative to the directory with capability-set dir. The entry has to exist already.
The new column masks are set to the ncols entries in the array cols. Like in sp append,
superfluous masks are ignored and missing masks are set to zero.
Required Rights:
SP MODRGT
Access to at least one column of the directory containing the entry to be
modified.
Error Conditions:
STD NOTFOUND:the specified name does not exist.
sp create
errstat
sp create(server, columns, dir)
capset *server;
char *columns[];
capset *dir;
Sp create creates a new Soap directory on the Soap Server specified by server . The column
names for the new directory are specified in columns , which is a NULL-terminated array of
strings. (There are no default names so columns must be specified.) The number of strings
determines the number of columns for the directory. The capability-set for the new directory
is returned in dir.
NB. It does not enter the new directory into another directory. (That is the function of
sp append. See sp mkdir (L) for a simple method of creating and installing a new directory.)
Required Rights:
Access to at least one column of the directory given as first argument.
Sp delete deletes the directory entry (which may itself be a directory capability) specified by
name . Name is a path name relative to the directory whose capability-set is given in dir.
Required Rights:
SP MODRGT
Access to at least one column of the directory containing the entry to be deleted.
sp discard
errstat
sp discard(dir)
capset *dir;
Sp discard is equivalent to performing an std destroy on the specified directory. This can
only be performed on a directory that is empty (i.e., has no entries in it).
Required Rights:
SP DELRGT for the directory to be destroyed.
Access to at least one column of the directory to be destroyed.
Error Conditions:
SP NOTEMPTY: directory to be deleted is not empty.
sp getmasks
errstat
sp getmasks(dir, name, ncols, cols)
capset *dir;
char *name;
int ncols;
rights bits cols[];
Sp getmasks returns in cols the column rights masks of the directory with path name name
relative to the directory with capability-set dir. If ncols is less than the number of columns
in the directory, only the masks for the first ncols columns will be returned in cols. If ncols
is greater than the number of columns in the directory, the remaining column masks in cols
will be set to zero.
Required Rights:
Access to at least one column of the directory containing the entry.
sp getseqno
errstat
sp getseqno(dir, seqno)
capset *dir;
sp seqno t *seqno;
Sp getseqno returns in seqno the 64-bit sequence number of the directory with capability-set
dir, if this is supported by the directory server. The sequence number is incremented with
each directory modification. This information is useful for backup programs (e.g.,
starch(U)) when making an incremental file system dump.
Required Rights:
Access to at least one column of the directory.
Error Conditions:
STD COMBAD sequence numbers are not supported by this server.
sp install
errstat
sp install(n, entries, capsets, oldcaps)
int n;
sp entry entries[];
capset capsets[];
capability *oldcaps[];
Sp install implements atomical update of a set of existing directory entries. They may be
entries in several different directories. The modify right is required for all the affected
directories. The operation only succeeds if all the capability-sets can be installed. Otherwise
no changes are made to any of the entries.
The parameter n specifies the number of elements in the three arrays. Each element of the
array entries contains the capset for the directory containing the entry to be updated and the
name of the directory entry within that directory which is to be updated. The type sp entry
is defined in soap.h as:
typedef struct {
capset se capset;
char *se name;
} sp entry;
For each directory entry described in entries the corresponding new capability-set in capsets
is installed. However, this is only done if the corresponding capability in oldcaps is either
NULL, or present (possibly as a restricted version) in the current capability-set. This allows
for optimistic concurrency control by letting one read a set of values and then ensure that
Error Conditions:
SP CLASH: a value in oldcaps did not match the corresponding
current entry.
sp list
#include "sp dir.h"
errstat
sp list(dir, dd)
capset *dir;
SP DIR **dd;
Sp list returns in dd the entire list of directory entries in the directory specified by dir. The
SP DIR data structure is defined in sp dir.h as follows:
typedef struct dirdesc {
capset dd capset;
int dd ncols;
int dd nrows;
char **dd colnames;
struct sp direct *dd rows;
int dd curpos;
} SP DIR;
struct sp direct {
char *d name; /* name (up to MAX NAME + 1) */
int d namlen; /* redundant, but compatible */
long *d columns; /* rights masks in the columns */
};
dd capset is initialized with dir, dd ncols with the number of columns in dir, and dd nrows
with the number of entries. The names of the columns are stored in dd colnames , which is a
string array of dd ncols entries. The entry dd rows is an array of dd nrows entries,
describing the (NULL-terminated) name in the row, the length of the name, and the rights
masks. The entry d columns is an array with dd ncols entries containing the rights masks
for each of the columns. The entry dd curpos is a magic cookie for the implementation of
the POSIX directory interface (see sp dir(L) and posix(L)).
sp lookup
errstat
sp lookup(dir, path, object)
capset *dir;
char *path;
capset *object;
Sp lookup returns in object the capability-set stored under the name name relative to the
directory dir. Warning: if the NULL-string is given as the path then the capability in dir is
for the directory required and so it is returned without checking to see if it is a valid
capability.
Required Rights:
Access to at least one column of the directory containing the object.
sp replace
errstat
sp replace(dir, name, cs)
capset *dir;
char *name;
capset *cs;
Sp replace performs a similar function to sp install but for a single directory entry. It
atomically updates the directory entry with path name name relative to directory dir. It
overwrites the capability-set for that entry with the one in cs.
Required Rights:
SP MODRGT
Access to at least one column of the directory containing the entry.
Error Conditions:
STD NOTFOUND: the specified path name does not exist.
sp setlookup
errstat
sp setlookup(n, in, out)
int n;
sp entry in[];
sp result out[];
For each directory entry described in in, the capability-set, the type capability, the update
sp traverse
errstat
sp traverse(dir, path, last)
capset *dir;
char **path;
capset *last;
Sp traverse returns in *path a pointer to the last component of the path name initially in
*path. It also returns in last the capability for the directory up until the last component of the
path (relative to the directory dir).
There are several special cases. The path name / which will return a zero length path name
in path and the capability-set for the root directory. The names dot(.) and dot-dot (..) at the
end of a path name will not be normalized away. A path name which contains no / character
will return in last the capability-set in dir and path will be unchanged.
Required Rights:
Access to at least one column of the directory containing each element of the
path.
See Also
chm(U), chbul(A), del(U), dir(U), mkd(U), get(U), put(U), rpc(L), soap(A), std(L),
std age(A), std destroy(U), std info(U), std copy(U), std restrict(U), std touch(U).
Name
sp dir − routines which implement the POSIX-like directory commands for the Soap Server
Synopsis
#include "sp dir.h"
void sp closedir(dd)
SP DIR *sp opendir(name)
struct sp direct *sp readdir(dd)
void sp rewinddir(dd)
void sp seekdir(dd, pos)
long sp telldir(dd)
Description
This set of routines implement an interface to the Soap Server which is similar to that
required by POSIX. However the management of the errno variable is not done. The
POSIX versions of these functions are available in the Amoeba libraries.
sp closedir
void
sp closedir(dd)
SP DIR *dd;
This routine closes the directory pointed to by dd and frees any associated resources. Dd
must point to a directory opened by sp opendir.
sp opendir
SP DIR *
sp opendir(name)
char *name;
This routine opens the directory whose path name is name , allocates storage, fills it with the
details of the directory and returns a pointer to the allocated data. This pointer must be used
in subsequent operations on the directory. If the name does not exist, is not a directory, or if
memory allocation fails or reading the directory fails, the routine returns the NULL-pointer.
sp rewinddir
void
sp rewinddir(dd)
SP DIR *dd;
This routine moves the current position within the directory specified by dd to the beginning.
A subsequent read will return the first entry in the directory.
sp seekdir
void
sp seekdir(dd, pos)
SP DIR *dd;
long pos;
This routine moves the current position within the directory specified by dd to the entry
number pos. A subsequent read will return the entry at position pos.
sp telldir
long
sp telldir(dd)
SP DIR *dd;
This routine returns the current position in the directory specified by dd.
See Also
posix(L), soap(L).
Name
sp mask − returns the information from the SPMASK environment variable
Synopsis
#include "amoeba.h"
#include "capset.h"
#include "soap/soap.h"
int
sp mask(ncols, cols)
int ncols;
long cols[];
Description
Sp mask reads the string environment variable SPMASK and returns the column masks
described therein in cols. The parameter ncols gives the size of the array cols. The function
returns the actual number of columns it filled in. If the environment variable is not defined it
returns 0xFF for the number of columns specified by ncols.
The value of SPMASK is used by several utilities to provide a default set of column masks for
new directory entries.
Environment Variables
SPMASK is expected to be a colon separated list of rights masks. The default base is 10 but
base 16 can be used by preceding the digits with 0x. For example,
SPMASK=0xff:0xf:0x2:0x8
See Also
soap(A), soap(L).
Name
sp mkdir − create and install a Soap directory
Synopsis
#include "amoeba.h"
#include "capset.h"
#include "soap/soap.h"
int
sp mkdir(start, path, colnames)
capset *start;
char *path;
char **colnames;
Description
This function creates a soap directory. It is merely a convenience, replacing calls to
sp traverse , sp create , sp mask , sp append and some error checking.
The arguments start and path together specify the name of the directory to be created.
Normally, start is SP DEFAULT and path is the absolute or relative path name for the
directory to be created. If start is not SP DEFAULT, path specifies the path name relative to
the directory specified by the capability-set start.
The argument colnames specifies the number of columns of the new directory and their
names. It should be a NULL-pointer or a pointer to a NULL-terminated array of strings
giving the column names. If it is a NULL-pointer, the new directory has three columns with
names owner, group and other. A directory can have at most SP MAXCOLUMNS columns
(this is typically 4).
The directory is appended to its parent directory with column masks computed by
sp mask (L).
Diagnostics
Error returns are those returned by the Soap routines called. In particular, path must not be
empty, and its last component must be a non-existent entry in an existing directory with
create and append rights. If an error occurs, all resources allocated by the call are released.
Environment Variables
SPMASK as used by sp mask (L).
#include "stdio.h"
main(argc, argv)
int argc;
char **argv;
{
char *err why();
int i;
int err;
int sts;
if (argc < 2) {
fprintf(stderr, "Usage: %s name ...\n", argv[0]);
exit(1);
}
sts = 0;
for (i = 1; i < argc; ++i) {
err = sp mkdir(SP DEFAULT, argv[i], (char **)NULL);
if (err != STD OK) {
fprintf(stderr, "%s: can’t create %s (%s)\n",
argv[0], argv[i], err why(err));
sts = 1;
}
}
exit(sts);
}
See Also
soap(L), sp mask(L).
Name
std − interface for standard server commands
Synopsis
#include "amoeba.h"
#include "module/stdcmd.h"
Description
The std module provides the interface to the standard commands which are supported by
nearly all servers. The purpose of these routines is to provide a standard way for programs to
deal with common aspects of servers. For precise details about rights bits and whether a
particular server handles a particular std call, consult the manual entry for the server.
Errors
The value of all the std functions is the error status. All the std stubs return only standard
error codes (as defined in stderr.h ). They all use transactions and therefore, in addition to the
errors described below, may all return the RPC error codes which relate to transaction errors.
Warning
Some older servers may return STD CAPBAD instead of STD DENIED when there are
insufficient rights for an operation.
std age
errstat
std age(cap)
capability *cap;
Std age is used to tell a server to perform a garbage collection cycle. The parameter cap is
the super capability for the server.
Error Conditions:
STD CAPBAD: the capability was invalid.
STD DENIED: insufficient rights.
std copy
errstat
std copy(server, orig, new)
capability *server;
capability *orig;
capability *new;
Std copy requests the server specified by server to make a copy of the object specified by the
capability orig and returns in new the capability for the new copy of the object. The server
should check that the type of object that it is being asked to copy is the type of object that it
manages. Servers that manage physical objects (such as disks) may not perform this
command.
Required Rights:
The server capability must have the server’s create right (if any) and the orig
capability must give read permission.
Error Conditions:
STD CAPBAD: the server capability or orig capability was invalid.
STD DENIED: the create right was not defined in server capability,
the read right was not defined in the orig capability.
STD COMBAD: the server specified by server does not perform
the copy operation.
std destroy
errstat
std destroy(cap)
capability *cap;
Std destroy requests the server to destroy the object specified by cap.
Required Rights:
Write and/or destroy rights.
Error Conditions:
STD CAPBAD: invalid capability.
STD DENIED: insufficient rights.
Error Conditions:
STD CAPBAD: invalid capability.
STD DENIED: insufficient rights.
std info
errstat
std info(cap, info, n, len)
capability *cap;
char *info;
int n;
int *len;
Std info requests from the server for (short) character array that describes the object
specified by the capability cap. The string is returned in the buffer info which is n bytes
long. The size of the buffer returned by the server is returned in *len. If the function returns
STD OK then the value returned in *len will be less than or equal to n and buf will contain
the complete status message.
If the buffer provided was too small to contain the data, the error STD OVERFLOW will be
returned along with the first n bytes of the information. The value returned in *len will be
the size the buffer needs to be to contain all the data.
If any other error is returned then the value of *len will not be modified.
Error Conditions:
STD CAPBAD: invalid capability.
STD DENIED: insufficient rights.
STD OVERFLOW:the info buffer was too small.
std restrict
errstat
std restrict(cap, mask, new)
capability *cap;
rights bits mask;
capability *new;
Std restrict requests the server managing the object specified by cap to produce a capability
with the rights removed that are not set in mask. The new capability is returned in new.
It should be noted that std restrict does not do a transaction with the server and verify the
capability if all the rights are present in the capability. This means that it can return STD OK
and a restricted capability even when the original capability had an invalid check field. In
this case the restricted capability will also be invalid.
Required Rights:
None.
Error Conditions:
STD CAPBAD: invalid capability.
std setparams
errstat
std setparams(cap, parambuf, paramlen, nparams)
capability *cap;
char *parambuf;
int bufsz;
int nparams;
Std setparams is used to set a number of the server’s runtime parameters associated with the
object specified by capability cap. The requested parameter assignments are specified in
parambuf which is bufsz bytes long. The number of parameter assignments in the buffer is
specified by nparams. For each assignment, there are two consecutive (NULL-terminated)
strings in the buffer: the parameter’s name , and its new value . Note that the server itself is
responsible for converting a supplied string value to integer, when a parameter’s type
requires this.
Error Conditions:
STD CAPBAD: invalid capability.
STD DENIED: insufficient rights.
STD ARGBAD: parameter value out of range,
or too many parameter settings.
std status
errstat
std status(cap, buf, n, len)
capability *cap;
char *buf;
int n;
int *len;
Std status requests the server for a character array containing current status information
about the server specified by the capability cap. The data is returned in the buffer buf which
is n bytes long. The size of the buffer returned by the server is returned in *len. If the
function returns STD OK then *len will be less than or equal to n and buf will contain the
complete status message.
If the buffer provided was too small to contain the data, the error STD OVERFLOW will be
returned along with the first n bytes of the status information. The value in *len will be the
size the buffer needs to be to contain all the data.
If any other error is returned then the value in len will not be modified.
NB. The character array is not NULL-terminated.
Required Rights:
Some servers require more rights than others depending on the sensitivity of the data.
Generally read permission is required, but some servers require that the super
capability be presented.
Error Conditions:
STD CAPBAD: invalid capability.
STD DENIED: insufficient rights.
STD OVERFLOW:input buffer was too small.
Error Conditions:
STD CAPBAD: invalid capability.
std ntouch
errstat
std ntouch(svr port, n, privbuf, num done)
port *svr port;
int n;
private *privbuf;
int *num done;
Std ntouch is used to tell the server whose service port is svr port not to garbage collect the
n objects specified by the private parts of capabilities in the array privbuf . On completion it
returns the number of objects successfully touched in num done. There is no upper limit on
the value of n, but std ntouch may use more than one RPC to execute the touches if n is
sufficiently large. The value of the function is STD OK if all the specified objects were
successfully touched. If any of the capabilities presented were rejected by the server then the
error status of the last error generated is returned.
Required Rights:
None.
Error Conditions:
STD ARGBAD: n <= 0.
STD CAPBAD: invalid capability.
See Also
std age(A), std copy(U), std destroy(U), std info(U), std params(A), std restrict(U),
std status(U), std touch(U).
Name
sun4m timer − microsecond resolution timer on the sun4m machines
Synopsis
#include "machdep/dev/sun4m timer.h"
Description
The timer resolution provided by sys milli(L) varies from milliseconds to deciseconds,
depending on the architecture on which it is executed. The SPARCstations of the type
sun4m provide a microsecond resolution timer which can be accessed by user programs. If
the timer is present on a host, its capability is published under the name usertimer in the
kernel directory of the host where it is available. Programs running on that host can map the
timer into their address space, thus avoiding the time delays due to system call overhead.
Error Conditions:
STD ARGBAD: No valid hostname specified.
STD NOMEM: no free virtual address space to map in timer segment.
errors from host lookup.
errors from dir lookup.
errors from seg map.
Error Conditions:
None.
Error Conditions:
None.
Error Conditions:
If the timer registers are not mapped in, calling this function will cause an exception.
Warnings
If more than one process maps in the usertimer and one or them modifies the behavior of the
timer then the other will not obtain meaningful timing information.
Example
An example of the use of these routines can be found in the test suite for Amoeba, in the file
src/test/performance/cpu/timertest.c.
See Also
sys milli(L).
Name
syssvr − the kernel systhread server client interface stubs
Synopsis
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
#include "server/systask/systask.h"
Description
These stubs provide a part of the programmer’s interface to the syssvr in the kernel. The
syssvr also supports certain soap(L), bullet (L) and std(L) commands. See syssvr (A) for
more details.
Errors
All functions return the error status of the operation. They return only standard error codes
(see stderr.h ). All the stubs involve transactions and so in addition to the errors described
below, all stubs may return any of the standard RPC error codes. If illegal parameters (such
as NULL-pointers or negative buffer sizes) are given, exceptions may occur rather than an
error status being returned. The behavior is not well defined under these conditions.
Functions
sys boot
errstat
sys boot(cap, kernelcap, commandline, flags)
capability *cap;
capability *kernelcap;
char *commandline;
int flags;
Sys boot requests that the kernel whose syssvr listens to capability cap should load the
kernel binary specified by kernelcap and start running it. The kernel is probably stored on a
file server. The commandline and flags will be passed to the new kernel in implementations
where this is possible. This request has the effect of terminating all current activity on the
host. In the future, one of the flags might be to migrate existing activity to other hosts before
rebooting.
sys chgcap
errstat
sys chgcap(cap, timeout, chkfields)
capability *cap;
interval timeout;
port chkfields[3];
Sys chgcap requests that the kernel whose syssvr listens to capability cap should change the
check fields of the kernel directory server, the syssvr and the process server to those specified
in the array chkfields . Unless changed by a subsequent call to sys chgcap, the kernel
directory server capability will revert to its default value after timeout seconds. This
function is used by the reservation system to reserve a kernel for exclusive use.
Note that if chkfield [0] is NULL then the kernel directory server capability will revert
immediately to its default value. This can be used to undo the effects of a previous
sys chgcap before the timeout has expired.
Required Rights:
SYS RGT ALL
sys kstat
errstat
sys kstat(cap, flag, buf, bufsz, num bytes)
capability *cap;
char flag;
char *buf;
bufsize bufsz;
int *num bytes;
Each Amoeba kernel provides a set of internal routines which print the current state of kernel
data as ascii strings into a buffer. Sys kstat requests that the kernel print some kernel data
selected by flag into buf (whose size is bufsz). The number of bytes of data returned in buf is
returned in num bytes .
Since kernels may have different configurations there is a special flag, ? which will return in
buf a list of flags supported by the kernel and a short description of each. With one exception
this set of flags corresponds exactly to those commands that can be given at the console using
the home key (CTRL− ) followed by the appropriate letter. The flag r is always illegal for
the sys kstat function.
Required Rights:
These depend on the sensitivity of the data or operation requested. Typically
SYS RGT READ is a minimum.
sys printbuf
errstat
sys printbuf(cap, buf, bufsz, offset, num bytes)
capability *cap;
char *buf;
bufsize bufsz;
int *offset;
int *num bytes;
Each Amoeba maintains a copy of messages printed on its console which originated within
the kernel. This is kept in a circular buffer which is guaranteed to fit in one RPC call.
Sys printbuf requests the kernel running on machine with the capability cap to return the
contents of its console buffer in buf. Bufsz specifies the size of buf in bytes. The function
returns in offset the starting position of the circular buffer and num bytes tells how many
bytes of data were returned in buf.
Required Rights:
SYS RGT READ
Example
The main use of these routines is in the programs kstat (A) printbuf (A) and reboot(A).
However, below is a simple program that demonstrates the use of some of the routines.
#include "amtools.h"
#include "sys/printbuf.h"
main(argc, argv)
int argc;
char *argv[];
{
capability mach;
errstat err;
char buf[30000];
int len, offset;
if (argc != 2) {
fprintf(stderr, "Usage: %s mach-cap\n", argv[0]);
exit(2);
}
The function printchar is not shown here. It simply takes care of unprintable characters in a
nice way.
See Also
bullet(L), kstat(A), printbuf(A), soap(L), std(L), std age(A), std destroy(U), std info(U),
std copy(U), std restrict(U), std touch(U), syssvr(A).
Name
sys milli − return time in milliseconds
Synopsis
#include "module/syscall.h"
unsigned long
sys milli()
Description
Sys milli returns the number of milliseconds since an unknown origin. It wraps after 2 ** 32
milliseconds, which is about 50 days. It is used for measuring time intervals by calling it
once at the start of an event and again at the end and comparing the difference.
Warnings
The granularity of the hardware clock is sometimes bigger than a millisecond.
Example
This function returns the time it takes to compute the sine of its argument, in milliseconds.
By computing it several times, it increases the accuracy of the measurement by reducing
errors due to the granularity of the system clock.
#define MUCH 10000
See Also
sun4m timer(L).
Name
sys newthread − low-level thread management
Synopsis
#include "thread.h"
Description
Beware! This manual page is only presented for completeness. Application programs
should use thread(L) to create threads and to manipulate glocal data.
The calls sys newthread , exitthread, sys getlocal and thread en preempt form the lowest
level interface to Amoeba’s multiple threads facility. In the current version of Amoeba,
threads by default run till completion: another thread in a process is only run if a thread exits,
executes a blocking system call (for example, see rpc(L)) or calls threadswitch (see
thread scheduling (L)). Preemptive scheduling must be requested explicitly using
thread enable preemption (see thread scheduling (L)). Programs should not rely on the
current default scheduling policy. They should always properly protect their access to shared
data with mutexes (see mutex (L)).
To allow the implementation of glocal data (see thread(L)), each thread has an associated
‘‘local value’’. This local value is stored in the kernel’s per-thread data. It can be retrieved
at any time by calling sys getlocal() . A thread’s local value is set once and for all when the
thread is created; the main thread’s local value is zero, for other threads the value is the third
argument passed to sys newthread .
A copy of the local value of the current thread is usually kept in the global variable
thread local. When the program only uses non-preemptive scheduling, this variable can be
kept up-to-date by the system call stubs (executing in user space): on entry, each stub saves
the current value of thread local on the stack; before returning, it restores thread local
from the stack. Certain architectures use a different strategy whereby thread local variable
is not saved; instead, a −1 is stored in it when the call returns. The routines that implement
thread(L) know this and test thread local for −1, and use sys getlocal to restore the true
local value. (There are also interactions with signal catchers that are taken care of by the
implementation of signals(L).)
When preemptive scheduling is used, a different mechanism is required. The system call
thread en preempt , which should only be called via thread enable preemption (see
thread scheduling (L)), has a parameter that will cause the kernel to update the thread local
pointer whenever a thread is being rescheduled.
Because the thread’s local value and the thread local variable are used by the
Functions
sys newthread
int
sys newthread(func, sp, local)
void (*func)();
struct thread data *sp;
struct thread data *local;
Sys newthread is the low level kernel stub to spawn a new thread in the current process. The
thread’s execution starts with a call to (*func)() with no arguments. The function should
never return. The stack pointer of the new thread is initialized to sp; the local value of the
new thread is initialized to local. Note that the stack pointer must point to at least 512 bytes
of space. Otherwise the calling program will be killed.
The new thread does not run immediately; the current thread keeps running until it exits,
blocks or calls threadswitch.
Sys newthread returns −1 upon failure (caused by lack of resources to create another thread
in the kernel) and zero upon success.
exitthread
void
exitthread(done)
long *done;
Exitthread is the low level kernel stub to exit a thread. When the thread is created by
thread newthread it is advisable to exit the thread using thread exit (see thread(L)), since
exitthread does not cleanup previously allocated stacks or memory blocks. The parameter
done specifies the address of a variable that will be set to one by the kernel as soon as the
thread has exited. This allows others threads to see when it is safe to free the resources that
were allocated for the thread. When the calling thread is a server and it is serving a client,
the client will receive an RPC FAILURE (see rpc(L)). Exitthread never returns.
sys getlocal
struct thread data *
sys getlocal()
Sys getlocal returns the thread local value for a thread (see above).
See Also
ansi C(L), posix(L), rpc(L), signals(L), thread(L), thread scheduling(L).
Name
tape − tape server stubs
Synopsis
#include "amoeba.h"
#include "module/tape.h"
Description
Along with the standard server stubs (see std(L)) the tape server stubs provide the
programmer’s interface to the tape server.
Errors
All functions return the error status of the operation. The user interface to the tape server
returns standard error codes (as defined in stderr.h ) and tape errors which are listed below.
Since all the stubs involve transactions they may return any of the standard RPC error codes.
A valid capability is required for all tape operations. Tape stubs will return STD CAPBAD if
an invalid capability is used. If illegal parameters (such as NULL-pointers or negative buffer
sizes) are given, exceptions may occur rather than an error status being returned. The
behavior is not well defined under these conditions.
The following tape errors are defined:
TAPE NOTAPE The tape unit is off-line or there is no tape available.
TAPE WRITE PROT The tape is write protected.
TAPE CMD ABORTED The last command has been aborted.
TAPE BOT ERR While repositioning, hit begin of tape marker.
TAPE EOF Found an EOF marker.
TAPE REC DAT TRUNC Tape record longer than expected.
tape load
errstat
tape load(cap)
capability *cap;
Tape load loads a tape on the tape unit identified by cap and positions it at the beginning of
medium.
tape read
errstat
tape read(cap, buf, record size, xread)
capability *cap;
bufptr buf;
bufsize record size;
bufsize *xread;
Tape read reads record size bytes from the tape specified by cap to buffer buf. The number
of bytes actually read is returned in xread. If record size bytes are requested from a tape
while the tape record is larger, tape read will fail (TAPE REC DAT TRUNC).
tape write
errstat
tape write(cap, buf, record size, xwritten)
capability *cap;
bufptr buf;
bufsize record size;
bufsize *xwritten;
Tape write writes record size bytes from buffer buf to the tape specified by cap. The
number of bytes actually written is returned in xwritten.
Tape write eof writes an End-Of-File marker on the tape specified by cap.
tape unload
errstat
tape unload(cap)
capability *cap;
Tape unload rewinds and unloads the tape specified by cap. This command does not wait
until the tape is physically rewound and unloaded. Some tape controllers cannot unload a
tape by themselves; they rewind the tape and wait for an operator to unload the tape
manually.
tape rskip
errstat
tape rskip(cap, count)
capability *cap;
int32 count;
Tape rskip skips count records on the tape specified by cap. Positive counts result in
forward record skipping, negative counts result in backward record skipping. Since EOF
markers are normal tape records for certain tape controllers skipping records might result in
skipping files without noticing. After a record skip, the tape file position may be uncertain.
tape rpos
errstat
tape rpos(cap, pos)
capability *cap;
int32 *pos;
Tape rpos returns the current tape record position in the variable pointed to by pos.
tape fpos
errstat
tape fpos(cap, pos)
capability *cap;
int32 *pos;
Tape fpos returns the current tape file position in the variable pointed to by pos.
tape erase
errstat
tape erase(cap)
capability *cap;
Tape erase formats the complete tape specified by cap. The current data is destroyed.
tape status
errstat
tape status(cap, buf, size)
capability *cap;
bufptr *buf;
bufsize size;
Tape status returns a NULL-terminated ASCII string in buffer buf which reports the status
of the tape specified by cap. The ASCII string size is size.
tape why
char *
tape why(err)
errstat err;
Tape why returns a NULL-terminated ASCII string which describes the tape error err. It
calls err why (see error(L)) if it does not recognize the error code.
Examples
The following example loads a tape, skips 10 records, reads a record of size bytes and
unloads the tape.
/* load tape */
if ((rv = tape load(cap)) != STD OK) {
printf("Cannot load tape: %s\n", tape why(rv));
exit(1);
}
See Also
error(L), std(L).
Name
thread − thread creation and thread memory management module
Synopsis
#include "amoeba.h"
#include "thread.h"
Description
The thread module provides the programming interface to create, destroy and manage
concurrent threads. Each thread can start executing a separate routine; they do not all have to
execute the same function. Each thread in a multi-threaded process shares the same address
space. It has its own stack and program counter but otherwise shares the text, data and bss
segments of the process. Because it is sometimes useful to have data global within a thread
but not accessible outside the thread, glocal data is provided. See the description of
thread alloc below for details of how to allocate and use glocal data.
The threads are currently scheduled non-preemptively by default. Preemptive scheduling
must be enabled explicitly using thread enable preemption (see thread scheduling (L)). It is
important to protect accesses to global data with mutexes (see mutex (L)). It is possible for a
thread to request that it be rescheduled using threadswitch (see thread scheduling (L)). This
can be very useful in the presence of non-preemptive scheduling.
NB. If a program is multi-threaded it is not safe to use UNIX emulation routines in more than
one thread of the program. UNIX is not multi-threaded and therefore the emulation is only
likely to be correct if confined to a single thread of a program. For example, a program with
several threads where one is in read waiting for input from a terminal and another does a
fork may well hang until the read is satisfied. The exit routine has been modified to force a
close on all descriptors, even if they are held by another thread and no guarantees are made
on the correctness of any resulting input or output if exit is called in such circumstances.
thread newthread
int
thread newthread(func, stsize, param, psize)
void (*func)();
int stsize;
char *param;
int psize;
Thread newthread spawns a new thread and starts it at function func. Thread newthread
allocates a thread stack of stsize bytes. Stsize must be at least 512 bytes or the calling
program will be killed. Parameters can be passed to the new thread via the
thread newthread parameters param and psize. Param is a pointer to the data structure to
pass, psize is the size of the data structure. Param must be allocated by a member of the
malloc family (see malloc (L)) since the clean up when the thread exits will free this
memory. Memory allocated using thread alloc cannot be used! When no parameters are
passed, param must be a NULL-pointer and psize must be zero. Once the thread exits, the
allocated stack and parameter area are freed. The function func is called as follows:
(*func)(param, psize)
char *param;
int psize;
If the called function returns, the thread exits.
Thread newthread returns zero upon failures (insufficient memory or out of threads),
otherwise a positive value is returned.
Note that not all threads are created equal. When a process first starts it consists of one
thread which starts the routine main() . If main returns then exit() is called which will
terminate the entire process immediately. If it is desired that main terminate and the other
threads continue then it must not return but call thread exit (described below).
Typically, when a new thread is created the parent continues to execute until it blocks.
However, if preemptive scheduling is enabled (see thread scheduling (L)) then the newly
created process will have the same priority as the current thread. This means at the next
event (such as an interrupt) the new thread may be scheduled.
thread exit
int
thread exit()
Thread exit stops the current thread. It then frees any glocal memory (allocated by
thread alloc/thread realloc ), the parameter area and the allocated stack before exiting.
Thread exit does not return. When the calling thread was a server thread, and it was still
serving a client, the client will receive an RPC FAILURE (see rpc(L)). If thread exit is
called in the last thread of a process, the process exits as well (see exitprocess (L)).
† To be precise, the variable must have global lifetime (‘static duration’ in Standard C jargon); it may
have local scope, i.e., it may be a static variable declared in a function.
thread realloc
char *
thread realloc(index, size)
int *index;
int size;
Thread realloc is used to change the size of the block of glocal memory associated with the
module reference number *index that was previously allocated by thread alloc. It allocates
a new glocal memory block of size bytes and initializes it to zero. It then copies to the new
memory block however much of the old data that will fit in it. When thread realloc is called
with *index equal to zero it operates as thread alloc.
Thread realloc returns a NULL-pointer on insufficient memory. It does not free the original
glocal data in this case so that it can still be accessed or the reallocation retried later.
Note Well: As a temporary hack, thread realloc never frees the previous copy of the data.
This must be done in the calling program using free (see malloc (L)). It is not recommended
that this routine be used until this feature is removed.
thread param
char *
thread param(size)
int *size;
Thread param returns the parameter pointer which was initially passed to the initial routine
of the current thread. It returns the size of the parameter in *size.
Warnings
The thread module uses the global variable thread local (see sys newthread (L)) for its
administration.
Example
The following example shows thread creation and the use of glocal memory. Ref nr is the
module reference number. Each time the signal handler is activated, the glocal data is
fetched which belongs to the running thread and this module.
#define NTHREADS 5
#define STKSIZE 8096
#define SIZE 100
static int ref nr;
main()
{
int i;
char * p;
for (i = 0; i < NTHREADS; i++) {
if ((p = malloc(sizeof (int))) == 0) {
printf("malloc failed\n");
exit(1);
}
*(int *)p = i;
if (!thread newthread(worker thread, STKSZ, p, sizeof (int))) {
printf("thread newthread failed\n");
exit(1);
}
}
/* Do not wait for threads to exit */
thread exit();
}
See Also
exitprocess(L), malloc(L), rpc(L), sys newthread(L), thread scheduling(L).
Name
thread scheduling − routines to control scheduling between threads
Synopsis
#include "thread.h"
void threadswitch()
void thread enable preemption()
void thread get max priority(max)
void thread set priority(new, old)
Description
These routines can be used to alter the scheduling algorithm used for selecting threads within
a process. They do not alter the scheduling algorithm between processes. Nor can they alter
the scheduling algorithm between kernel threads. The threads in the kernel always have a
higher priority than those of user processes. All user processes have equal priority and are
scheduled round-robin with time-slicing.
The default scheduling between threads within a process is non-preemptive. Threads run
until they block or terminate. Threads block when they make a blocking system call. For
example, an RPC (see rpc(L)), a grp(L) function or a mu lock (see mutex (L)). Since most
library routines, e.g., sema up, sema down, printf and open, do an RPC or use mutexes to
protect shared variables, it is highly likely that a library call will block. When non-
preemptive scheduling between threads is used it is sometimes necessary for a process to
voluntarily give up control so that another thread can run. That functionality is provided by
threadswitch .
However, it is possible to enable preemptive scheduling between threads and to assign
different priorities to threads. In this case the highest priority thread that is runnable will be
scheduled when the process is selected to run. The lowest priority for a thread is 0. The
highest is that returned by thread get max priority . Time-slicing takes place between
threads. The effect of priorities is confined to within the process. It is not the case that the
highest priority thread from all processes is selected.
Functions
threadswitch
void
threadswitch()
Threadswitch causes the current thread to stop running and the scheduler to be called. If
preemptive scheduling is disabled (the default case) then if there is another runnable thread in
the same process it will be the next thread to run in that process. Note, however, that it may
Example
The following demonstrates how to enable preemptive scheduling and to assign priorities to
threads.
#include "amoeba.h"
#include "thread.h"
void
mate(p, sz)
char * p;
int sz;
{
long old;
main()
{
long old;
long max;
See Also
thread(L).
Name
tios − termios-style terminal control interface
Synopsis
#include "amoeba.h"
#include "termios.h" /* or "posix/termios.h" */
Description
This module contains the capability-based counterparts of the termios function calls (see
posix(L)). These operations are supported by several servers, including ax(U), xterm and the
console driver in the kernel. The functions tc marshal and tc unmarshal help servers with
packing and unpacking a struct termios The functions tc frombsd and tc tobsd provide
conversion from and to structures used by BSD’s tty ioctl’s. Similarly the tc fromsysv and
tc tosysv functions provide conversion from and to the structures used by UNIX System V
ioctl’s.
tios *
errstat
tios drain(tty)
capability *tty;
errstat
tios flush(tty, queue selector)
capability *tty;
int queue selector;
errstat
tios getattr(tty, tp)
capability *tty;
struct termios *tp; /* out */
errstat
tios sendbrk(tty, duration)
capability *tty;
int duration;
errstat
tios setattr(tty, optional actions, tp)
capability *tty;
int optional actions;
struct termios *tp; /* in */
There is a one-to-one correspondence between these calls and the POSIX calls, replacing the
file descriptor parameter to the POSIX call by a capability pointer parameter to the tios call,
and the tc name prefix with tios . See the POSIX standard for an explanation of the
semantics. See posix(L) for Amoeba-dependent information.
Error Conditions:
STD COMBAD: operation not supported by server.
STD CAPBAD: invalid capability.
STD DENIED: insufficient rights.
RPC errors are also possible, as well as other errors depending on the server implementation.
tios getwsize
errstat
tios getwsize(tty, width, length)
capability *tty;
int *width; /* out */
int *length; /* out */
This routine returns the dimensions of the screen area of the terminal device. This provides
support for window systems where windows may have a variable size and screen-based
programs need to enquire of the size of the screen area.
char *
tc unmarshal(buf, tp, same byte order)
char *buf; /* in */
termios *t; /* out */
int same byte order;
Function tc marshal packs a struct termios value into the buffer argument, and returns a
pointer to the next free byte in the buffer. Function tc unmarshal unpacks the buffer into a
struct termios variable, and returns a pointer to the next data byte in the buffer. Both use the
flag ‘‘same byte order’’ to determine whether to swap bytes or not. There is no buffer
overflow detection, nor are other error conditions detected.
tc frombsd, tc tobsd
void
tc frombsd(tp, sgp, tcp, ltcp, local mode p)
struct termios *tp; /* out */
struct sgttyb *sgp; /* in */
struct tchars *tcp; /* in */
struct ltchars *ltcp; /* in */
int *local mode p; /* in */
void
tc tobsd(tp, sgp, tcp, ltcp, local mode p)
struct termios *tp; /* in */
struct sgttyb *sgp; /* out */
struct tchars *tcp; /* out */
struct ltchars *ltcp; /* out */
int *local mode p; /* out */
These functions convert a struct termios from/to a couple of BSD-style tty control blocks.
No error conditions are detected. NOTE: These functions are not present in the library if it
was compiled with the flag SYSV.
Warnings
Most servers only implement tios (get,set)attr , not the other tios stubs.
Code compiled for UNIX must include Amoeba’s termios.h to get the proper definition of
struct termios , but UNIX’s sys/ioctl.h to get the proper definition of the ioctl structs.
The tc (un)marshal interface may go away or change in the future. If you decide to write a
server that implements the tios functions, you should consider using ail(U) instead of
tc (un)marshal .
There should be no reason to use tc (to,from)bsd .
Note
The kernel library contains the tc * functions but not the tios * ones.
Example
For an example of using tc (un)marshal , see the source for ax(U). For examples of using the
tios stubs and the only uses of tc (from,to)bsd , see the source for ioctl (see posix(L)) and
ax(U).
See Also
IEEE Std 1003.1-1988 (POSIX), section 7.
BSD manual pages ioctl (2), tty(4).
ail(U), ax(U), posix(L).
Name
tod − the Time-Of-Day server interface stubs
Synopsis
#include "amoeba.h"
#include "module/tod.h"
Description
This module provides the interface to the time of day service. There may be more than one
time of day server on an Amoeba network and it is possible to select a particular server, or
simply use the default server when getting or setting the time.
Functions
tod defcap
errstat
tod defcap()
This routine is used to set the time of day server to be used by calls to get or set the time. It
finds the capability for a time-of-day server as follows. It uses the TOD capability
environment variable if it exists. Otherwise it looks up DEF TODSVR as defined in
ampolicy.h (which is typically /profile/cap/todsvr/default). The capability is stored in static
data inside the module. Be careful when trying to use multiple time of day servers.
The function returns STD OK if it successfully looked up the capability in the directory
server. Otherwise it returns the error status from the directory server look-up.
tod setcap
void
tod setcap(tod cap)
capability *tod cap;
Tod setcap sets to tod cap the pointer to the capability for the time of day server to be used
in subsequent calls to tod gettime and tod settime. The address of the capability is stored in
static data inside the module. Be careful when trying to use multiple time of day servers.
Error Conditions:
RPC errors if the server is not available.
tod settime
errstat
tod settime(sec, msec)
long sec;
int msec;
Tod settime sets the time of the time-of-day server as selected by either tod defcap or
tod setcap .
Tod settime calls tod defcap if it has not already been called, so it is not necessary to
explicitly call tod defcap or tod setcap before tod settime.
Required Rights:
All rights bits must be on.
Error Conditions:
STD DENIED if any rights are not on.
STD CAPBAD if the capability is invalid.
RPC errors if the server is not available.
Warnings
The granularity of the clock is server dependent. Time values are truncated as opposed to
rounded.
If the capability whose address is handed to tod setcap is overwritten, the new value will be
used in subsequent calls to tod gettime and tod settime.
If tod gettime or tod settime fails, it undoes the effect of any previous tod setcap .
See Also
date(U), ctime(L), posix(L), rpc(L).
Name
uniqport − generate a random port
Synopsis
#include "module/rnd.h"
void uniqport(p)
void uniqport reinit()
Description
Routines to generate unique RPC and group communication ports and check fields.
uniqport
void
uniqport(p)
port * p;
Uniqport produces a non-NULL, random port. In general the result should be unique to the
system. It is useful for servers that need to create new check fields or a new port to listen to.
It uses a random number server to get the seed for the random number generator.
uniqport reinit
void
uniqport reinit()
Uniqport reinit causes the next call to uniqport to choose a new seed for the generation of
random numbers.
Environment Variables
If set, RANDOM determines which random number server to use when generating the seed.
Otherwise the default random server is used.
Warnings
It is possible that the port generated is not unique within the system. If it is to be used as a
port then it may be necessary to test to see if anyone is already listening to that port before
using it. This is done by doing a std info (see std(L)) on the port with a short locate timeout
(say 2 seconds).
port listen;
uniqport(&listen);
See Also
rnd(L), rpc(L).
Name
vdisk − the virtual disk server client interface stubs
Synopsis
#include "amoeba.h"
#include "cmdreg.h"
#include "stderr.h"
#include "module/disk.h"
Description
Along with the standard server stubs (see std(L)) the virtual disk stubs provide the
programmer’s interface to the Virtual Disk Server. The Virtual Disk Server allows the user
to specify the size of the block they wish to use in the call. This is convenient for certain
applications which work most effectively with a particular block size and do not wish to be
concerned with the physical reality. The block size must be a power of 2. To enforce this it
must be specified in the call as the logarithm base 2 of the desired block size. It must be
greater than D PHYS SHIFT (see disk/disk.h ).
Types
The file server/disk/disk.h (which is included by module/disk.h) defines the type disk addr
which is a disk address. It is implemented as a large integer.
Access
Access to the disk is determined on the basis of rights in the capability given with each
command.
The following rights are defined in Virtual Disk Server capabilities:
RGT WRITE permission to write to the disk.
RGT READ permission to read the disk.
Errors
All functions return the error status of the operation. The user interface to the Virtual Disk
Server returns only standard error codes (as defined in stderr.h ). All the stubs involve
transactions and so in addition to the errors described below, all stubs may return any of the
standard RPC error codes. A valid capability is required for all operations and so all
Functions
disk getgeometry
errstat
disk getgeometry(cap, geom)
capability *cap;
geometry *geom;
Disk getgeometry returns in geom the disk geometry information for the first piece of a
virtual disk. It is only intended for use on bootp partitions (which are physical disks). These
consist of exactly one piece. However if applied to a virtual disk consisting of more than one
piece it returns the geometry of the first piece.
Required Rights:
RGT READ
Error Conditions:
STD SYSERR: If the data returned was insufficient for a full
geometry struct.
disk info
errstat
disk info(cap, buf, size, cnt)
capability *cap;
dk info data *buf;
int size;
int *cnt;
Disk info returns information about the physical disk partitions that comprise the virtual disk
specified by cap. The data returned consists of:
unit number of the virtual disk (type: long)
disk address of the first block in the partition available to the user (type: disk addr)
number of disk blocks available in the partition (type: disk addr)
It returns the data in buf (which contains room for size sets of data). The structure
dk info data is defined in the include file server/disk/disk.h . Disk info returns in cnt the
number of units of information returned. NB. If more partitions are available than fit in the
buffer provided it will return the number asked for without complaint or warning.
Error Conditions:
STD SYSERR: If the data returned was insufficient for a full
dk info data struct.
disk read
errstat
disk read(cap, l2vblksz, start, numblocks, buf)
capability *cap;
int l2vblksz;
disk addr start;
disk addr numblocks;
bufptr buf;
Disk read reads exactly numblocks of the size specified by l2vblksz (i.e., the disk block size
is 2l 2vblksz ), starting at block number start, from the virtual disk specified by cap. The data is
returned in the buffer pointed to by buf. If it fails to read exactly the right amount of data
then it will return an error status.
Required Rights:
RGT READ
Disk size
errstat
disk size(cap, l2vblksz, size)
capability *cap;
int l2vblksz;
disk addr *size;
Disk size returns in size, the number of blocks of the size specified by l2vblksz (i.e., the disk
block size is 2l 2vblksz ), available on the virtual disk specified by cap.
Required Rights:
RGT READ
See Also
dread(A), dwrite(A), std(L), std age(A), std copy(U), std destroy(U), std info(U),
std restrict(U), std touch(U), vdisk(A).
Name
virtcirc − virtual circuits, full-duplex interprocess communication channels
Synopsis
#include "amoeba.h"
#include "semaphore.h"
#include "vc.h"
Description
The virtual circuit module provides a collection of routines to manage virtual circuits.
Virtual circuits are full-duplex interprocess communication (IPC) channels. A virtual circuit
consists of an input and an output channel, which can be concurrently read or written.
Virtual circuit users write data to the output channel and read data from the input channel. A
close on an output channel causes an end of file (EOF) to be transmitted. This does not flush
the current contents of the local output and remote input channels. A close on an input
channel causes a hangup (HUP) to be transmitted and the current contents of the local input
and remote output channels are flushed.
Internally, the input and output channels consist of a thread and a circular buffer. The input
channel is a client thread which receives data from the remote server and writes it to the input
circular buffer. The output channel is a server thread which reads data from the output
circular buffer and transmits it to the remote client.
The virtual circuit structure and various other constants (for example, VC IN, VC OUT) are
defined in the header file vc.h.
vc close
void
vc close(vc, which)
struct vc *vc;
int which;
Vc close closes one or both channels of the virtual circuit. A closed channel may not be read
or written any more. Which can either be VC IN, VC OUT or VC BOTH. VC IN closes the
input channel, VC OUT closes the output channel and VC BOTH closes both channels. To
break down a connection completely, both channels of the virtual circuit must be closed.
Closing VC OUT does not flush the contents of the local output and remote input channels.
Closing VC IN does flush the local input and remote output channels. By default vc close
operates synchronously: it waits until the connection is completely broken before returning.
When VC ASYNC is added to the parameter which, vc close operates asynchronously: it
does not wait until the connections are broken down. Once both channels are closed, the
virtual circuit reference pointer may not be accessed any more.
vc read, vc readall
int
vc read(vc, buf, size)
struct vc *vc;
bufptr buf;
int size;
int
vc readall(vc, buf, size)
struct vc *vc;
bufptr buf;
int size;
The routines vc read and vc readall read size bytes from the input channel into the buffer
buf. The routine vc read reads at least one byte and at most size bytes. The routine
vc write
int
vc write(vc, buf, size)
struct vc *vc;
bufptr buf;
int size;
Vc write writes size bytes from the buffer buf to the output channel.
Vc write returns −1 on failure (i.e., broken connection, closed remote input channel).
Otherwise it returns size.
vc getp, vc getpdone
int
vc getp(vc, buf, blocking)
struct vc *vc;
bufptr *buf;
int blocking;
vc putp, vc putpdone
int
vc putp(vc, buf, blocking)
struct vc *vc;
bufptr *buf;
int blocking;
vc avail
int
vc avail(vc, which)
struct vc *vc;
int which;
Vc avail returns the number of bytes immediately available in the input or output channel. If
VC IN is selected, the number of free bytes in the input channel is returned. If VC OUT is
selected, the number of free bytes in the output channel is returned.
Vc avail returns −1 when the specified channel is closed.
vc setsema
void
vc setsema(vc, sema)
struct vc *vc;
semaphore *sema;
Vc setsema attaches an external semaphore on the input channel. Each time a byte arrives
on the input channel or when the input channel is closed, the semaphore is upped via
sema up (see semaphore(L)). The semaphore is initialized to the number of data bytes
available in the input channel. This routine is the virtual circuit equivalent of cb setsema
(see circbuf (L)).
vc warn
vc warn(vc, which, rtn, arg)
struct vc *vc;
int which;
void (*rtn)();
int arg;
Vc warn sets a warning routine for interesting events. When the input channel is selected
(which == VC IN), the routine rtn will be called whenever the input channel is not empty or
when the input channel breaks/closes down. If the output channel is selected (which ==
VC OUT), the routine will be called whenever the output channel state changes to empty or
when the output channel breaks/closes down.
The specified argument arg will be passed as argument to rtn.
Example
The following example creates a virtual circuit, the client writes a password through the
virtual circuit (‘‘Foo’’), which is checked by the server, and some data is transferred. On
illegal passwords or failures, the connection is directly broken by the server.
/* read 4 bytes */
if (vc readall(vc, buf, 4) != 4) {
/* on failures, close down asynchronously */
vc close(vc, VC BOTH|VC ASYNC);
exit(1);
}
/* Check password */
if (strcmp(buf, "Foo")) {
/* not ok, close down */
vc close(vc, VC BOTH|VC ASYNC);
exit(1);
}
/* well, do something */
do something(bp, size);
See Also
circbuf(L), rpc(L), semaphore(L).
Name
x11 − the X server interface stubs
Synopsis
#include "amoeba.h"
#include "server/x11/Xamoeba.h"
Description
These routines provide control over X servers to user programs. They are not part of the
standard X interface provided by the X11 libraries.
Functions
x11 reinit
errstat
x11 reinit(server)
capability *server;
X11 reinit sends a message to the X server asking it to re-initialize itself. As a result all
connections are dropped, all windows are closed and various resources are released. It is
commonly used when logging out, or before logging in, to ensure a clean display without
dangling connections.
x11 shutdown
errstat
x11 shutdown(server)
capability *server;
X11 shutdown asks the addressed X server to terminate. This results in an orderly shutdown
of all connections, after which the server exits. It is useful when shutting down a
workstation.
Diagnostics
Only RPC errors can be returned.
main(argc, argv)
int argc;
char **argv;
{
capability scap;
char sname[100];
See Also
X documentation.
. ar toport, 114
ar topriv, 114
A ASCII, 114
asctime, 147, 149
abort, 165 asynchronous communication, 358
aborting system calls, 361 atomic commit, 127
ACK, 7, 26-29, 33, 36, 45, 60 atomic file creation, 6
ACK description file, 29-33 AWK, 105
ACKFE environment variable, 29 ax, 385, 408
aging, 152 AX HOST, 385
AIL, 4, 7, 10, 17, 20, 22, 68, 76
AIL code generator, 76 B
AIL reference manual, 68
Ajax, 7, 38, 295 back end, 26
AJAX DEBUG environment variable, 295 back-end, 29
AJAX TRACE environment variable, 295 BASIC, 7, 25
amake, 7, 83-104 BAWK, 105
am exerrors.h, 167 b create, 127, 131
AMEX GETDEF, 169 b delete, 127, 133
AMEX MALLOC, 169 b disk compact, 127-128
AMEX NOCAPS, 169 b fsck, 127, 129
AMEX NOHOST, 169 binary semaphores, 275
AMEX NOPROG, 169 b insert, 127, 132
AMEX PDLOOKUP, 169 blocking signals, 358
AMEX PDREAD, 169 blocking system calls, 302
AMEX PDSHORT, 169 b modify, 127, 131
AMEX PDSIZE, 169 bprintf, 117
AMEX SEGCREATE, 169 b read, 127, 130
AMEX STACKOV, 169 broadcast, 5, 183
ampolicy.h, 113 broken connection, 421
ANSI C, see STD C b size, 127, 129
a.out, 36, 167 bss segment, 5
ar cap, 114 b sync, 127, 129
architecture, 170 buf get cap, 119
architecture identifier, 308 buf get capset, 119
architecture-independence, 119 buf get int16, 119
ARCHSIZE, 308 buf get int32, 119
argc, 192 buf get long, 14, 17
argv, 192 buf get objnum, 119
ar port, 114 buf get pd, 119, 293, 308, 313
ar priv, 114 buf get port, 119
ar tocap, 114 buf get priv, 119
G I
garbage collection, 152 immutable, 127
gax, 385 in-core segment, 351
getcap, 177 initial program counter, 308
getcwd, 297 initial stack pointer, 308
getinfo, 174, 178, 304, 349 input channel, 421
get-port, 10, 14, 21, 333 Internet Protocol, 197, 203, 209, 216, 223
getreq, 3-4, 6, 10-11, 13-14, 21, 109, 332- interprocess communication, 332
333, 335-338, 359 interrupt, 358
gid, 295 ioctl, 408
glocal data, 5, 392, 400-403 IP, 197, 203, 209, 216, 223
GMT, 147 ip, 197, 209
gmtime, 147, 149 ip host lookup, 194-195
GNU, 7, 25 ip ioc getconf, 209, 211
W
WORK environment variable, 295
write protected, 395
X
X windows, 427
x11 reinit, 427
x11 shutdown, 427
Z
zic, 147-148
zoneinfo, 147