9P(7)

πŸ“¦ version 0.2, in nixos 26.05, nixpkgs unstable

πŸ’– NAME πŸ’–

9P β€” Simple Distributed File System

okay so real talk, this page lives in the Miscellaneous Information Manual, and it's all about 9P, the protocol bestie. buckle up πŸ’…


✨ DESCRIPTION ✨

9P is a protocol that implements a distributed file system. it gives you the primitives to manage (create, read, write and delete) sets of files remotely. and here's the iconic part β€” those files don't necessarily have to actually live on a disk. they might be, for example, synthesised on demand from external sources. main character energy fr fr 🌈

A client transmits requests (T-messages) to a server, which returns replies (R-messages) back to the client. the combined act of transmitting a request of a particular type and receiving a reply is called a transaction of that type. periodt.

Every message is a sequence of bytes, mostly grouped into one, two or four integer fields, transmitted in little-endian order (least significant byte first, iykyk). data items of larger or variable length get represented by a two-byte field specifying the length, followed by the actual data. the only exception to this rule is QIDs β€” thirteen-byte-long objects β€” which are sent as-is. no notes. πŸ’€

Text strings are represented with a two-byte count plus the sequence of UNICODE codepoints encoded in UTF-8. text strings in 9p are not NUL-terminated. the NUL-terminator is illegal in all text strings and thus excluded from paths, user names and so on. do not try it 🫢

Fields are hereafter denoted as

type[1] tag[2] fid[4]

to indicate that type is one byte long, tag two and fid four. Strings are denoted as name[s] and are sent on the wire as

length[2] string[length]

A qid, described later, is a 13-byte value that is sent on the wire as

type[1] version[4] path[8]

πŸŽ€ MESSAGE STRUCTURE πŸŽ€

Every message has a header with these fields, no cap:

len[4] type[1] tag[2]

where len indicates the overall length of the message, including itself; type is one byte indicating the type of the message; and tag is a number chosen by the client that uniquely identifies the request. then follows an optional body whose structure depends on the type of the message. slay πŸ’…

The message types are as follows (the header is omitted for brevity):

⭐ version β€” Negotiate the version and maximum message size

msize[4] version[s]
msize[4] version[s]

The version request must be the first message sent, and the client cannot issue further requests until it receives the Rversion reply. tag should be NOTAG (-1 or 255). The client suggests a msize (the maximum size for packets) and the protocol version used, and the server replies with a msize smaller or equal to the one proposed by the client. the version string must always begin with the two characters "9P". if the server doesn't understand the client required version, it should reply with a Rversion using the version string "unknown" and not use a Rerror. okay? okay πŸ’–

⭐ attach β€” Populate the namespace

fid[4] afid[4] uname[s] aname[s]
qid[13]

The attach message binds the given fid to the root of the file tree identified by aname. uname identifies the user, and afid specifies a fid previously established by an auth message β€” or the special NOFID value (defined as (u32int_t)~0) if authentication is not required. πŸ¦‹

⭐ clunk β€” Close fids

fid[4]
⟨empty response⟩

Once a fid has been clunked (closed) it becomes "free" and the same value can be used for subsequential walk or attach requests. πŸŽ€

The actual file on the disk is not removed unless it was opened with the ORCLOSE flag. important!!

⭐ error β€” Return an error string

⟨no request⟩
ename[s]

The Rerror message is used to return an error string describing the failure of a request. the tag indicates the failed request.

Note that there isn't a Terror request for obvious reasons, and it's not possible for a server to reply to a Tversion or Tflush using Rerror. πŸ™ˆ

⭐ flush β€” Abort an ongoing operation

oldtag[2]
⟨empty response⟩

Given the asynchronous nature of the protocol, the server may respond to the pending request before responding to the Tflush, and it's possible for a client to send multiple Tflush for the same operation. the client must wait to receive a corresponding Rflush before reusing oldtag for subsequent messages.

If a response for oldtag is received before the Rflush reply, the client must assume that the operation was completed with success (fid allocated, files created, ...). if no response is received before the Rflush, then the transaction is considered to have been successfully cancelled. ✨

Note that the tag of this request and the corresponding reply is NOT oldtag but a new tag value. πŸ’€

⭐ walk β€” Traverse a file tree

fid[4] newfid[4] nwname[2] nwname*(wname[s])
nwqid[2] nwqid*(qid[13])

The nwname components are walked in order starting from fid (which must point to a directory) and, if successful, newfid is associated to the reached file. 🧚

It is possible for fid and newfid to be equal β€” in this case the fid is "mutated" β€” otherwise newfid must be unused. as a special case, a walk of zero components duplicates the fid.

If the first element cannot be walked for any reason, an Rerror is returned. otherwise, Rwalk is returned with a number of qids equal to the files visited by the walk. a client can thus detect a partial walk when the replied nwqid number is not equal to the nwname field in the request. only when walk returns successfully will newfid be affected. πŸ’œ

A maximum of 16 components can be used per walk request. don't push it πŸ”₯

⭐ open β€” Prepare a fid for I/O

fid[4] mode[1]
qid[13] iounit[4]

mode determines the type of I/O:

  • 0x00 (OREAD) β€” open the file for reading
  • 0x01 (OWRITE) β€” open the file for writing
  • 0x02 (ORDWD) β€” open the file for both reading and writing
  • 0x03 (OEXEC) β€” open for exec

Additionally, the following flags can be or'ed to mode:

  • 0x10 (OTRUNC) β€” truncate the file before opening
  • 0x40 (ORCLOSE) β€” remove the file upon clunk

The returned iounit is the optimal blocksize for I/O. πŸ“

⭐ create β€” Create a file

fid[4] name[s] perm[4] mode[1]
qid[13] iounit[4]

The call attempts to create a file named name in the directory identified by fid according to perm, and then to open it with mode into the given fid.

It is illegal to use an already opened fid or to attempt to create the "." or ".." entries. 😭

⭐ read β€” Read data at offset

fid[4] offset[8] count[4]
count[4] data[count]

fid must have been prepared for I/O with a previous open call. the returned count is zero when reaching end-of-file and may be lesser than what was requested. 🫢

Directories are a stream of stat structures, as described in stat, and for them the read request message must have offset equal to zero, or the value of offset in the previous read on the directory plus the number of bytes returned in the previous read. so basically, it's not possible to seek into directories β€” except for rewinding. πŸ’…

⭐ write β€” Write data at offset

fid[4] offset[8] count[4] data[count]
count[4]

fid must have been prepared for I/O with a previous open or create call. the returned count is the amount of data actually written and may differ from the one in the request. slay 🌸

⭐ stat β€” Get file status

fid[4]
stat[n]

The stat structure is made of the following fields:

  • size[2] β€” total byte count of the following data
  • type[2] β€” for kernel use
  • dev[4] β€” for kernel use
  • qid[13] β€” server unique identifier of the file
  • mode[4] β€” permissions and flags
  • atime[4] β€” last access time
  • mtime[4] β€” last modification time
  • length[8] β€” length of file in bytes
  • name[s] β€” file name (must be "/" if the file is the root directory of the server)
  • uid[s] β€” owner name
  • gid[s] β€” group name
  • muid[s] β€” name of the user who last modified the file
Note that size is always present, even in the wstat call. while it may be considered redundant, it's kept to simplify the parsing of the stat entries in a directory. iconic design choice tbh πŸ‘‘

⭐ wstat β€” Change file attributes

fid[4] stat[n]
⟨empty response⟩

fid must have been prepared for writing with a previous open or create call.

The stat structure is the same one described in stat. ✨

The stat structure sent reflects what changes the client wishes to make to the given fid. to leave some fields unchanged, use an empty string or the maximum allowed value for integral fields. for example, to avoid changing the permission of the fid use 0xFFFFFFFF, or (uint32_t)-1. πŸŽ€

⭐ remove β€” Remove and clunk fid

fid[4]
⟨empty response⟩

After a remove call, even if an error is returned, the fid is closed. yeah, it's like that πŸ’€


🌟 SEE ALSO 🌟

utf8(7), kamid(8)


and that's the whole page bestie, so good right?? 🫢 this one came from Debian, dated July 30, 2021 β€” go read utf8(7) and kamid(8) next, or don't, i'm not your mom πŸ’–βœ¨πŸŽ€

πŸ“¦ where this page comes from