=== Intoduction to VSTa file protocol ===
In VSTa, a server is a process that creates one or more message ports
and presumably receives on them every once in a while. Other than
that, they're no different from any other process. They run as user
code and can make any system calls that other processes can. They can
be clients to other servers as well as being a server itself. For
example, if you run vstafs on your hard disk, then vstafs is a client
to wd and server to sh, ls, gcc, etc, whatever process is using that
filesystem. In this way the process has two different "faces" to the
operating system. Any process can behave this way. (What would you
call 'em, servents? Or cliers?)
Servers are also invoked just as you would other processes. If you
decide you need to use the floppy drive in the middle of a session,
# fd &
Don't forget the &, as otherwise you connect your terminal to fd, and
fd doesn't interact on stdin or stdout. There's nothing saying it
couldn't, however, as a matter of convention, servers run unattended
and don't usually expect to talk to a human user.
To be considered a server, a process creates a message port using the
syscall msg_port(). There are basically two ways to call it:
handle = msg_port(###, NULL);
handle = msg_port(NULL, &portname);
You would use the first syntax if you wanted to establish the server
on a specific global port name, which you would specify as the first
parameter. There are a couple of officially assigned port names in the
VSTa headers. You can, of course, pick any number you like, but
msg_port() will fail if that number is already taken.
Mostly likely you'll want to use the second form. In this case, the
system will pick a port number for you and store it in the variable
whose address you pass in the second parameter. This is usually what
you want. Then you can give that port number to other processes.
That's the number they'll use to connect to your server.
The VSTa standard service namer allows servers to associate their port
numbers with text identifiers in a global, hierarchical namespace. So,
for instance, programs can use //fs/root:a/b/c instead of
//1025:a/b/c. The library function namer_register() can do the drudge
work for you. Lookup helper functions are also included in the
The value that's returned by msg_port is your process's private handle
on to that message port. Don't pass this value to other processes; it
has no meaning in their context. It is not the same as the global port
Note that you can create multiple ports if you want to. The limit is
defined in the kernel; I think it is set to 4 currently. Most servers
will only need one message port.
To handle messages that come in from clients, you would use the
struct msg m;
x = msg_receive(handle, &m);
This is a blocking call that waits for the next message to come in
from any client (or potential client.) The message structure has the
m_sender : an opaque number used to identify the client
m_op : this is the operation code. It's either a number that the
client gave in its msg_send() call, or a kernel-generated (and
trusted) M_ system message. See below.
m_arg : a single argument to go with the operation.
m_arg1 : another argument for the operation. (Not usually used.)
m_nseg : The number of data buffers that the client has sent. Usually
only one, but clients can send up to four buffers in a single call.
Servers should handle clients who send more than one buffer at a time.
(There are library functions to help with this.)
m_seg : An array of buffer definitions that the client sent.
m_buf : A quick way to reference the first buffer.
m_buflen : A quick way to reference the length of the first buffer.
If msg_receive() returns a negative number, the receive operation was
interrupted by an event while waiting for a client.
Bit 31 of m_op, known as M_READ, has a special meaning. It is used to
indicate the direction of data flow between the client and server. If
clear, it means that the client is sending buffers to the server. If
set, it means that the server is sending buffers to the client. It's
the client's responsibility to get the value of M_READ right. The
server will usually mask it out. If a client asks for FS_READ instead
of FS_READ|M_READ, for example, the server will work just fine;
however, the kernel will never transfer the buffers to the client.
Once a msg_receive() has returned successfully, the client will be
blocked waiting for some kind of response from the server. In general,
the two possible responses (but see below) are msg_reply(), indicating
success (you can pass an integer back, as the return value for
msg_send()), and msg_err(), with which you can pass back an error
Message operation codes that are at or below the predefined constant
M_RESVD are reserved for the kernel; ordinary clients can't
deliberately send them with msg_send(). The most important ones are
M_CONNECT : a client is trying to connect. The m_buf contains a
trusted array of the user's permissions. In this case, you don't use
msg_reply() to indicate success. You rather use msg_accept() instead.
Errors are still reported with msg_err().
M_DISCONNECT : a client has disconnected. This message is a bit
different in that it is asynchronous: no reply is necessary; the
client is already gone.
M_DUP : a client has requested a clone of this connection instance.
Essentially, the client becomes two initially identical clients, each
independent from then on. This is necessary for fork() as well as the
clone() syscall. In general, servers should let their clients clone
M_ABORT : the client is no longer requesting the operation. Happens
when the client is interrupted (by event, for example) while they're
waiting for the server to respond. You'll likely only need to do any
processing for this message if you poll for messages in between the
msg_receive() and the msg_reply()/msg_err() of a client operation
(i.e., you're asynchronous). In that case, you need to clear the
asynchronous request and msg_reply() to the M_ABORT. If you're not
asynchronous, too late; you've already replied and your message has
been thrown out. In this case, just msg_reply() to the M_ABORT.
M_ISR : this message is sent by the kernel when a hardware interrupt
has occurred that your server has requested. You must have enabled the
IRQ in order to receive this message.
Messages above M_RESVD are user messages. The most common ones are the
FS_WSTAT - these handle reading and writing of attributes
Character special: (pipes, console, serial, TCP, etc.) above plus:
Files: all above plus
FS_ABSWRITE - optional, these two combine a seek and r/w operation
Directories/file systems: all above except FS_WRITE and FS_ABSWRITE
In the case of FS_OPEN, the client descends a level into the
hierarchy. The client is then "inside" the file they opened. The other
two manipulate the current directory, but don't change the client.
FS_READ on a directory reads a list of names of items that may be
Any other messages can be defined; the messages numbers are not
interpreted by the kernel at all. I would encourage the creation of
additional classes of message operations if a server has actions that
do not fit well into the already existing message codes. (Some VSTa
servers, such as sema, insist on using the FS_ message codes even
though they don't really make sense in what the server has to do.)
Hopefully this will get you started. Also don't be afraid to look at
the source of some of the VSTa servers. They're well commented.
A couple of other things: message ports exist in a process-wide
context; they aren't associated with any particular thread. Any thread
may receive messages on any port. If two threads msg_receive on the
same port at the same time, one will get the next message, the other
will get the message after that. However, the most common use of
threads in servers is to have one run the message loop while the
others do client operations.