=== 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, just do: # 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 library. 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 name. 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 msg_receive syscall: 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 following fields: 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 string. 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 the following: 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 themselves. 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 filesystem messages: All devices: FS_STAT FS_WSTAT - these handle reading and writing of attributes Character special: (pipes, console, serial, TCP, etc.) above plus: FS_READ FS_WRITE Files: all above plus FS_SEEK FS_ABSREAD FS_ABSWRITE - optional, these two combine a seek and r/w operation Directories/file systems: all above except FS_WRITE and FS_ABSWRITE plus FS_OPEN FS_RENAME FS_REMOVE 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 FS_OPEN'ed. 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.