tiger
       |~|_|~|_|~|             |~|_|~|_|~|
        \       /               \       /
         |     |                 |     |
         |  _  |~|_|~|_|~|_|~|_|~|  _  |
         | | | |                 | | | |
         |  ~  |    /~~~~~~~\    |  ~  |
         |     |   |~~~~|~~~~|   |     |
         |     |   |====|====|   |     |
         |     |   |~~~~|~~~~|   |     |
    /~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
   <    We design castles, YOU build them!   >
    \_______________________________________/

SImple network File System

Specification V0.1.1

Basics

Sifs is network filesystem, which once tried to be simple. It requires stream connection with flow control, such as TCP. For now it will not survive server reboot.

Client and server talk in packets. All numbers are passed in network byte order. No lengths include padding, lengths include any headers.

Each packet starts with header (defined below), packets are padded so that total length is 4*n. Header layout is:

u32 reqsize   - size of packet including header
u32 reqhandle - handle of request, this is used to match request with reply
u32 command   - MSB distingushes between request and reply

Some commands will contain structure called extension block, which is variable-sized. (Its type will be used as xblock in following text). Extension blocks contains entries key=val; in key's low 16 bit, there's length of whole xblock.

u32 (.......key....)(....length....)      => xblock is length bytes long
length bytes of  data
padding so that this part is n*4 bytes long

Some commands will need to pass around strings. Strings will be passed in following format:

u16 length (including itself)
bytes string (not NULL-terminated!)
padding so that total length of string is n*4 bytes

After normal command, packet with corresponding reqhandle, command set to SFS_OK or SFS_ERROR is returned.

File names

To save confusion with national alphabets etc, all names passed through this thing are in UTF-8. Character #0 is path separator. Some other characters may be unsupported on some servers. At least 0-9a-z_- should be supported. There may also be a limit on name length; but it should be big enough (at least 64 characters).

Information about strange directories (such as '.', '..' on UNIX) is never passed around here: they mey not be stated, they do not appear on readdir listing etc.

Every time path is passed, it is relative to some handle. Assume that handle is corresponding to directory '/foo'. Then, combination of handle and empty string is reference to '/foo'. Combination of handle and 'bar' is reference to '/foo/bar'. No leading nor trainling #0's, please.

Handles

There are three kinds of handles here:

There are three kinds of people: those, who can count and those who can't.

Commands

Open

header (command = SFS_OPEN)
u32 flags
u32 dirhandle
u32 version
string path (relative to dirhandle) 
xblock

Flags are bitfield, defined values are:

Some more values may be used if SFS_O_WRITE is used:

If command is successfull, it returns packet:

header (command = SFS_OPEN_OK)
u32 handle
u32 length (length == -1 means length has no sense)
u32 version
u32 flags

File versions are passed around here so that caching on client side is possible. Flags can contain one of SFS_C_FLUSHCACHE (which means that you must forget any cached data you have about that file) and SFS_C_UNCACHEABLE (you may not attempt to cache this file).

Close

header (command = SFS_CLOSE)
u32 handle (any)
xblock

This command takes single parameter, handle you do not need anymore.

Read

header (command = SFS_READ)
u32 handle, position, length

Note that this protocol has nothing like seek. It is not needed, as position is passed all the time. If file is unseekable (character device), 0 is passed as position.

Data a returned in packet with command code set to SFS_READDATA. Less than length bytes may be returned (even 0), indicating end of file. 0 bytes will be also returned if you attempt to read past end of file.

ForceRead

header (command = SFS_FORCE_READ)
u32 handle, position, length
string path

Force read just reads - not caring if someone else has file open for writing etc. You can get almost anything in this case. Path is relative to given handle, reply is same as in case of read.

Write

header (command = SFS_READ)
u32 handle, position
bytes data

Note that length does not need to be passed as it is clear from reqsize.

Unlink

header (command = SFS_UNLINK)
u32 dirhandle
string path
xblock

This operation has single argument, path of file to unlink relative to dirhandle. Unlink will also happily unlink empty directory. Unlinking of files/directories that are in use is undefined.

MakeTemplate

header (command = SFS_MAKETEMPLATE)
string fields
xblock

This function creates template for later use in readdir/wstat. You should close template when you stop using it.

Each field is a byte, specifying what you want readdir to return (or wstat to set). Bytes must be sorted, and you'll get fields in that order. For every field you specify here, you get data as specified in following table.
#Symbolic nameDescriptionReturn
10SFS_D_NAMEName of filestring name
1SFS_D_SIZESize of fileu32 filesize
8SFS_D_TYPEType of fileu16 filetype
2SFS_D_CTIMECreation timeu32 seconds from 1.1.1970
3SFS_D_ATIMELast access timeu32 seconds from 1.1.1970
4SFS_D_MTIMELast modification timeu32 seconds from 1.1.1970
5SFS_D_IDFile identification (if available)u32 major, minor, inode
7SFS_D_LINKCNTNumber of references to this fileu16 links
6SFS_D_UNIXPERMSOwnership of file and its permissions, unix-styleu32 owner, group, permissions
9SFS_D_SL_NAMEName of file this link points to (if softllink) or emptystring name

File types are SFS_T_FILE, SFS_T_DIR, SFS_T_SYMLINK, SFS_T_CHARDEV, SFS_T_BLKDEV, SFS_T_FIFO, SFS_T_SOCKET. We could count seconds from something else than 1.1.1977, what about 18.5.1977 ;-)

MakeTemplate responds with following packet:

header (command = SFS_NEWTEMPLATE)
u32 template
string fields
xblock

Save template, it is your ticket for new syscalls. In fields, you can read what server is going to tell you. Not everything you asked may be there: for example server may be unable to provide that information.

Readdir

header (command = SFS_READDIR)
u32 dirhandle
u32 readdirhandle (or 0 if first read on this directory)
u32 template
u16 numentries
string path
xblock

Read entries from directory, template says which entries we want. Num is max in number of directory entries you want to see. Because filehandle can be used in place of dirhandle (here), readdir with filehandle and numentries = 1 is somehow similar to what unix calls stat.

For each readdir (when no error is there), you get following packet:

header (command = SFS_DIRENTRIES)
u32 readdir handle (or 0 if end of list)
u16 numentries
bytes record defined by template used

Stat

header (command = SFS_STAT)
u32 dirhandle or filehandle
u32 template
string path
xblock

Give info about file/directory, template says which entries we want. (For files, it is somehow similar to readdir with count = 1). It returns following packet (unless error is detected):

header (command = SFS_STATDATA)
bytes record defined by template used

Wstat

header (command = SFS_WSTAT)
u32 dirhandle or filehandle
u32 template
string path
bytes statdata

Format of statdata is defined by template used.

Rename

header (command = SFS_RENAME)
u32 h1: dirhandle or filehandle source
u32 h2: dirhandle or filehandle destination
string source path (relative to h1)
string destination path (relative to h2)
xblock

Copy file

header (command = SFS_COPYFILE)
u32 h1: dirhandle or filehandle source
u32 h2: dirhandle or filehandle destination
string source path (relative to h1)
string destination path (relative to h2)
xblock

Nop

header (command = SFS_NOP)
xblock

This command will probably be used for testing if the other side is alive: TCP will detect if other side is dead iff it has any unsent data. So sending NOP every ten minutes is enough for detection of dead clients etc. Nothing is sent back after this command.

Caching

Hold

header (command = SFS_HOLD)
u32 handle
string path (relative to handle)
xblock

Command hold means you are interested in given file/directory. You get its handle, and you'll be informed using lost command when it changes. If it is changed, you get notification, but your handle is valid, anyway. It can even be passed to hold, again, if you want to know about next change. You should close this handle when you stop using it.

If command is successfull, it returns packet:

header (command = SFS_HOLDING)
u32 handle
xblock

If object is changed, you get packet:

header (command = SFS_LOST)
u32 handle
xblock

Please consider following sequence: hold( /tmp ) (SFS_OK returned) open( /tmp/delme ) (and because /tmp/delme did not exists, it is effectively create) (SFS_LOST returned, which must come before SFS_OK - directory was modified before operation was completed, after all...).

Reply codes

Ok report

header (command = SFS_OK)
xblock

Operation completed successfully. This is normally returned after each command (if not stated otherwise in command's description.

Error report

header (command = SFS_ERROR)
u32 code
xblock

If there is a error performing certain operation, this packet is returned. Code is one of:

FIXMEs

Would be nice to describe read-only variant of this.

How is mkdir done? SFS_O_DIRECTORY with SFS_O_CREATE?


Photo is courtesy of Philip Greenspun. Everything except photo is copyright 1997 Pavel Machek, and should be distributed in terms of GPL.

Pavel Machek
pavel@atrey.karlin.mff.cuni.cz