Vibed, trust at your own peril. # 9P2026 Protocol Specification **Status:** Draft **Extends:** 9P2000 **Version string:** `9P2026` --- ## 1. Overview 9P2026 is a backward-compatible extension of 9P2000 that modernizes the wire format and adds targeted capabilities for parallel operation, amortized writes, bulk directory reads, and filesystem event notification. It preserves Plan 9's core ethos: simplicity of mechanism, explicitness of semantics, and composition through fileservers rather than protocol complexity. ### 1.1 Design Principles - No new hidden state. All new capabilities are requested explicitly by the client and confirmed by the server. - Servers that implement a subset of extensions remain valid. Clients discover capability through negotiation and error responses, not capability bitmasks. - The protocol remains transport-agnostic. No changes depend on shared memory, OS-specific mechanisms, or anything below the message boundary. - Event notification is a convention over existing read semantics, not a new message class. ### 1.2 Changes from 9P2000 | Area | Change | |---|---| | Tag field | u16 → u32 | | Timestamps | u32 (epoch seconds) → u64 (nanoseconds since epoch) | | Open flags | New `OASYNC` flag | | New messages | `Treaddir`/`Rreaddir`, `Trenegotiate`/`Rrenegotiate`, `Tsync`/`Rsync` | | Pipelining | Mandated for servers; servers MUST support out-of-order response | | Events | Convention over blocking `Tread` on `events` files (no new messages) | --- ## 2. Wire Format ### 2.1 Primitive Types All multi-byte integers are little-endian. Sizes are in bytes unless noted. | Notation | Type | Size | |---|---|---| | `u8` | unsigned 8-bit integer | 1 | | `u16` | unsigned 16-bit integer | 2 | | `u32` | unsigned 32-bit integer | 4 | | `u64` | unsigned 64-bit integer | 8 | | `str` | u16 length prefix followed by UTF-8 bytes | 2 + n | | `qid` | See §2.3 | 13 | | `stat` | See §2.4 | variable | ### 2.2 Message Header Every message begins with: ``` size[4] u32 Total message size including this field type[1] u8 Message type (see §3) tag[4] u32 Client-assigned request identifier ``` **Note:** The tag field is 4 bytes in 9P2026, compared to 2 bytes in 9P2000. This change takes effect only after successful `Tversion`/`Rversion` negotiation of `9P2026`. During `Tversion` itself the tag MUST be `NOTAG` (see §2.6); the 2-byte vs 4-byte distinction does not apply to that message. **NOTAG:** The reserved tag value for messages that precede tag assignment is `0xFFFFFFFF`. **NOFID:** The reserved fid value indicating no fid is `0xFFFFFFFF` (unchanged from 9P2000). ### 2.3 Qid A Qid uniquely identifies a file on a server. Structure is unchanged from 9P2000: ``` type[1] u8 File type flags (see below) vers[4] u32 Version; incremented on file modification path[8] u64 Server-unique file identifier ``` Qid type flags: | Flag | Value | Meaning | |---|---|---| | `QTDIR` | 0x80 | Directory | | `QTAPPEND` | 0x40 | Append-only file | | `QTEXCL` | 0x20 | Exclusive-use file | | `QTMOUNT` | 0x10 | Mounted channel | | `QTAUTH` | 0x08 | Auth file | | `QTTMP` | 0x04 | Non-backed (temporary) file | | `QTSYMLINK` | 0x02 | Symbolic link (9P2000.u extension) | | `QTFILE` | 0x00 | Plain file | ### 2.4 Stat The stat structure is used in `Rstat`, `Twstat`, and `Rreaddir`. In 9P2026, timestamp fields are 64-bit nanoseconds since the Unix epoch (January 1, 1970 00:00:00 UTC). ``` size[2] u16 Total byte count of the following fields type[2] u16 For kernel use dev[4] u32 For kernel use qid[13] qid Server's unique identifier for the file mode[4] u32 Permissions and flags (see §2.5) atime[8] u64 Last access time, nanoseconds since epoch mtime[8] u64 Last modification time, nanoseconds since epoch length[8] u64 Length of file in bytes name str File name; "/" for root uid str Owner name gid str Group name muid str Name of user who last modified the file ``` The `size` field counts all bytes after itself. Servers MUST set `type` and `dev` to zero; clients MUST ignore them. **Permissions (mode field):** | Bit | Meaning | |---|---| | 0x80000000 | Directory | | 0x40000000 | Append-only | | 0x20000000 | Exclusive use | | 0x04000000 | Authentication file | | 0x00000100 | Owner read | | 0x00000080 | Owner write | | 0x00000040 | Owner execute | | 0x00000020 | Group read | | 0x00000010 | Group write | | 0x00000008 | Group execute | | 0x00000004 | Other read | | 0x00000002 | Other write | | 0x00000001 | Other execute | ### 2.5 Open Mode Flags Used in `Topen` and `Tcreate`: | Flag | Value | Meaning | |---|---|---| | `OREAD` | 0x00 | Open for reading | | `OWRITE` | 0x01 | Open for writing | | `ORDWR` | 0x02 | Open for reading and writing | | `OEXEC` | 0x03 | Open for execution | | `OTRUNC` | 0x10 | Truncate file on open | | `OCEXEC` | 0x20 | Close on exec | | `ORCLOSE` | 0x40 | Remove on clunk | | `OASYNC` | 0x80 | Async write mode (9P2026) | `OASYNC` may be combined with `OWRITE` or `ORDWR`. Its semantics are specified in §6. ### 2.6 Special Tag Values | Value | Meaning | |---|---| | `0xFFFFFFFF` | `NOTAG` — used for `Tversion` only | --- ## 3. Message Types ### 3.1 Type Assignments T-messages (client to server) have even type values. R-messages (server to client, responses) have odd values equal to T-message type plus one. | Value | Name | Description | |---|---|---| | 100 | `Tversion` | Negotiate protocol version and msize | | 101 | `Rversion` | | | 102 | `Tauth` | Request auth fid | | 103 | `Rauth` | | | 104 | `Tattach` | Attach to server | | 105 | `Rattach` | | | 106 | — | Reserved (Terror, unused) | | 107 | `Rerror` | Error response to any T-message | | 108 | `Tflush` | Abort a pending request | | 109 | `Rflush` | | | 110 | `Twalk` | Walk directory tree | | 111 | `Rwalk` | | | 112 | `Topen` | Open a fid | | 113 | `Ropen` | | | 114 | `Tcreate` | Create a file | | 115 | `Rcreate` | | | 116 | `Tread` | Read from a fid | | 117 | `Rread` | | | 118 | `Twrite` | Write to a fid | | 119 | `Rwrite` | | | 120 | `Tclunk` | Forget a fid | | 121 | `Rclunk` | | | 122 | `Tremove` | Remove a file | | 123 | `Rremove` | | | 124 | `Tstat` | Request file metadata | | 125 | `Rstat` | | | 126 | `Twstat` | Write file metadata | | 127 | `Rwstat` | | | 128 | `Treaddir` | Read directory entries with stat (9P2026) | | 129 | `Rreaddir` | | | 130 | `Trenegotiate` | Renegotiate msize (9P2026) | | 131 | `Rrenegotiate` | | | 132 | `Tsync` | Flush buffered async writes (9P2026) | | 133 | `Rsync` | | --- ## 4. Existing Messages This section documents all 9P2000 messages with their 9P2026 wire formats. The only structural change to existing messages is the tag field width (u16 → u32) and timestamp width in stat (u32 → u64). ### 4.1 Tversion / Rversion Negotiate protocol version and maximum message size. ``` Tversion size[4] u32 type[1] u8 = 100 tag[4] u32 MUST be NOTAG (0xFFFFFFFF) msize[4] u32 Client's proposed maximum message size version str "9P2026" Rversion size[4] u32 type[1] u8 = 101 tag[4] u32 NOTAG msize[4] u32 Agreed maximum message size (≤ client's proposed) version str "9P2026" if accepted, "unknown" if rejected ``` The server MUST respond with a version string of `"9P2026"` if it supports this specification, or `"unknown"` if it does not, in which case the client MAY retry with `"9P2000"` using 2-byte tags. The agreed `msize` applies to all subsequent messages in both directions, including the header. Neither side may send a message larger than `msize` bytes. Minimum `msize` is 256. A `Tversion` resets the connection: all outstanding fids are clunked and all pending requests are aborted. ### 4.2 Tauth / Rauth Request an auth fid prior to attach. May be elided if the server does not require authentication. ``` Tauth size[4] u32 type[1] u8 = 102 tag[4] u32 afid[4] u32 Fid to use for auth protocol uname str User name aname str Attach target name Rauth size[4] u32 type[1] u8 = 103 tag[4] u32 aqid[13] qid Qid of the auth file ``` If the server does not require authentication it MUST return `Rerror` in response to `Tauth`. The client then attaches without an afid (using `NOFID`). ### 4.3 Tattach / Rattach Establish a session root. ``` Tattach size[4] u32 type[1] u8 = 104 tag[4] u32 fid[4] u32 Fid to assign to the root afid[4] u32 Auth fid, or NOFID uname str User name aname str Attach target name Rattach size[4] u32 type[1] u8 = 105 tag[4] u32 qid[13] qid Qid of the attach root ``` ### 4.4 Rerror Returned by the server in place of any R-message to indicate failure. ``` Rerror size[4] u32 type[1] u8 = 107 tag[4] u32 Tag of the failing T-message ename str Error description ``` The tag in `Rerror` MUST match the tag of the `T`-message that caused the error. The `ename` is a UTF-8 string; no error code is defined at the protocol level. ### 4.5 Tflush / Rflush Abort a pending T-message. ``` Tflush size[4] u32 type[1] u8 = 108 tag[4] u32 oldtag[4] u32 Tag of the message to abort Rflush size[4] u32 type[1] u8 = 109 tag[4] u32 ``` The server MUST respond to `Tflush` with `Rflush`. It MUST NOT subsequently send a response for the flushed tag. If the original request completed before the flush arrived, the server sends `Rflush` anyway and the original response is discarded. The client MUST NOT reuse the old tag until `Rflush` is received. A `Tflush` for an async-write fid (see §6) does not imply `Tsync`; buffered writes that have not yet been sent MAY be discarded. ### 4.6 Twalk / Rwalk Walk a sequence of path elements from an existing fid, assigning a new fid at the walk target. ``` Twalk size[4] u32 type[1] u8 = 110 tag[4] u32 fid[4] u32 Starting fid (must not be open) newfid[4] u32 Fid to assign at walk target; may equal fid nwname[2] u16 Number of path elements (0..16) nwname × wname str Path elements Rwalk size[4] u32 type[1] u8 = 111 tag[4] u32 nwqid[2] u16 Number of qids returned (0..nwname) nwqid × wqid qid Qid for each successfully walked element ``` If `nwname` is 0, the walk clones `fid` into `newfid` without moving. If `nwname > 0` and `nwqid < nwname`, the walk partially succeeded; `newfid` is not created. If `nwqid == nwname` the walk fully succeeded and `newfid` is set to the final element. Maximum path elements per walk: 16. Clients that need to walk deeper must chain multiple walks. ### 4.7 Topen / Ropen Open a fid for I/O. ``` Topen size[4] u32 type[1] u8 = 112 tag[4] u32 fid[4] u32 mode[1] u8 Open mode flags (see §2.5) Ropen size[4] u32 type[1] u8 = 113 tag[4] u32 qid[13] qid Qid of the opened file iounit[4] u32 Suggested I/O unit; 0 means no suggestion ``` `iounit` is advisory. If non-zero it indicates the maximum size of a guaranteed-atomic read or write. Requests larger than `iounit` may be split by the server. If `OASYNC` is set in `mode`, async write semantics apply (§6). The server MUST return `Rerror` if it does not support `OASYNC`. ### 4.8 Tcreate / Rcreate Create a new file and open it. ``` Tcreate size[4] u32 type[1] u8 = 114 tag[4] u32 fid[4] u32 Fid of the directory in which to create name str Name of the new file perm[4] u32 Permission bits mode[1] u8 Open mode flags Rcreate size[4] u32 type[1] u8 = 115 tag[4] u32 qid[13] qid Qid of the new file iounit[4] u32 ``` After `Rcreate`, `fid` refers to the newly created file, not the directory. `perm` is ANDed with the parent directory's permissions. If the `DMDIR` bit (0x80000000) is set in `perm`, a directory is created and `mode` MUST be `OREAD`. ### 4.9 Tread / Rread Read data from an open fid. ``` Tread size[4] u32 type[1] u8 = 116 tag[4] u32 fid[4] u32 offset[8] u64 Byte offset for the read count[4] u32 Maximum bytes to return Rread size[4] u32 type[1] u8 = 117 tag[4] u32 count[4] u32 Bytes returned data[count] u8[] ``` A `Tread` on a directory returns stat records sequentially packed. Clients must use `offset` and the byte count returned to iterate. Prefer `Treaddir` (§5.1) over `Tread` on directories. A `Tread` returning `count == 0` indicates EOF. For files opened `OASYNC`, a `Tread` on the same fid is permitted; the server MUST ensure the read reflects all previously acknowledged writes (i.e., writes for which `Rwrite` has been sent) but need not reflect buffered writes not yet acked. Reads on stream resources (pipes, network connections) block until data is available. `count == 0` indicates the stream is closed at the remote end. ### 4.10 Twrite / Rwrite Write data to an open fid. ``` Twrite size[4] u32 type[1] u8 = 118 tag[4] u32 fid[4] u32 offset[8] u64 Byte offset for the write count[4] u32 Bytes to write data[count] u8[] Rwrite size[4] u32 type[1] u8 = 119 tag[4] u32 count[4] u32 Bytes written ``` For files opened without `OASYNC`, the server MUST NOT respond with `Rwrite` until the data is durable (or as durable as the server's backing store guarantees). This is unchanged from 9P2000. For files opened with `OASYNC`, semantics differ; see §6. Servers MAY return a `count` less than requested. Clients MUST retry with the remainder. ### 4.11 Tclunk / Rclunk Release a fid. After `Rclunk` the fid is invalid. ``` Tclunk size[4] u32 type[1] u8 = 120 tag[4] u32 fid[4] u32 Rclunk size[4] u32 type[1] u8 = 121 tag[4] u32 ``` Clunking a fid opened `OASYNC` does NOT imply sync. Buffered writes not yet acknowledged by the server MAY be discarded. Clients MUST call `Tsync` before `Tclunk` if they require durability. ### 4.12 Tremove / Rremove Remove a file and clunk its fid. The fid is invalid after `Rremove` regardless of success or failure. ``` Tremove size[4] u32 type[1] u8 = 122 tag[4] u32 fid[4] u32 Rremove size[4] u32 type[1] u8 = 123 tag[4] u32 ``` ### 4.13 Tstat / Rstat Request metadata for a file. ``` Tstat size[4] u32 type[1] u8 = 124 tag[4] u32 fid[4] u32 Rstat size[4] u32 type[1] u8 = 125 tag[4] u32 nstat[2] u16 Byte count of the following stat (redundant per spec, retained) stat stat See §2.4 ``` ### 4.14 Twstat / Rwstat Modify metadata for a file. Fields set to their "don't care" values are left unchanged by the server. ``` Twstat size[4] u32 type[1] u8 = 126 tag[4] u32 fid[4] u32 nstat[2] u16 Byte count of the following stat stat stat See §2.4 ``` Don't-care values: | Field | Don't-care | |---|---| | name | empty string | | uid, gid, muid | empty string | | mode | 0xFFFFFFFF | | atime, mtime | 0xFFFFFFFFFFFFFFFF | | length | 0xFFFFFFFFFFFFFFFF | | qid, type, dev | ignored | ``` Rwstat size[4] u32 type[1] u8 = 127 tag[4] u32 ``` --- ## 5. New Messages ### 5.1 Treaddir / Rreaddir Read directory entries with full stat data in a single round trip. `fid` must refer to an open directory (opened with `OREAD`). ``` Treaddir size[4] u32 type[1] u8 = 128 tag[4] u32 fid[4] u32 An open directory fid offset[8] u64 Byte offset into the directory stream count[4] u32 Maximum bytes of stat data to return Rreaddir size[4] u32 type[1] u8 = 129 tag[4] u32 count[4] u32 Bytes of stat data returned data[count] u8[] Sequence of packed stat records (§2.4) ``` The `data` field is a sequence of `stat` records packed end-to-end, each preceded by its `size[2]` field. Clients iterate by consuming `size` bytes at a time. `offset` is an opaque byte offset into the server's directory stream, not an entry index. Clients MUST use the byte offset of the last consumed record plus its size as the offset for the next `Treaddir`. The only valid starting offset is 0. `count == 0` in `Rreaddir` indicates the end of the directory. Servers that do not implement `Treaddir` MUST return `Rerror`. Clients MUST fall back to `Tread`/`Tstat` in that case. **Interaction with concurrent modification:** Servers provide no snapshot guarantee. Entries added or removed during iteration may appear or be absent. Clients MUST tolerate this. ### 5.2 Trenegotiate / Rrenegotiate Renegotiate `msize` mid-session without resetting connection state. ``` Trenegotiate size[4] u32 type[1] u8 = 130 tag[4] u32 msize[4] u32 Proposed new msize Rrenegotiate size[4] u32 type[1] u8 = 131 tag[4] u32 msize[4] u32 Agreed new msize (≤ proposed) ``` The agreed `msize` takes effect immediately upon the client receiving `Rrenegotiate`. All messages sent after that point, in both directions, MUST conform to the new `msize`. A client SHOULD NOT issue `Trenegotiate` while requests other than `Tflush` are in flight. The server MAY return `Rerror` if outstanding requests exist. After `Rrenegotiate` all existing fids remain valid. Servers that do not support `Trenegotiate` MUST return `Rerror`. The previous `msize` remains in effect. Minimum `msize`: 256. Neither side may propose an `msize` smaller than 256. ### 5.3 Tsync / Rsync Flush all buffered async writes on a fid to durable storage. ``` Tsync size[4] u32 type[1] u8 = 132 tag[4] u32 fid[4] u32 An open fid with OASYNC Rsync size[4] u32 type[1] u8 = 133 tag[4] u32 ``` The server MUST NOT respond with `Rsync` until all writes previously issued on `fid` for which `Rwrite` has been sent are durable. After `Rsync` returns, the file's data is as persistent as the server's backing store guarantees. `Tsync` on a fid not opened `OASYNC` is a no-op: the server returns `Rsync` immediately. `Tsync` on a non-file fid (e.g. a directory) MUST return `Rerror`. Durability guarantee: `Rsync` provides the same durability guarantee as a synchronous `Rwrite` in standard 9P2000. It is not stronger. --- ## 6. Async Write Mode (OASYNC) ### 6.1 Semantics When `OASYNC` is set in `Topen` or `Tcreate`, the server MAY return `Rwrite` before the data is durable. The server takes ownership of the data as soon as `Rwrite` is sent; the client MUST NOT assume the data is durable until a subsequent `Rsync`. This enables a client to maintain a pipeline of outstanding `Twrite` messages without waiting for each `Rwrite`. Combined with the mandated pipelining (§7), this is the primary mechanism for improving raw write throughput. ### 6.2 Client Obligations - The client MUST call `Tsync` before `Tclunk` if durability is required. - The client MUST call `Tsync` before relying on the written data being visible to other clients or after a crash. - The client MUST NOT reuse the data buffer for a write until it has received the corresponding `Rwrite`. ### 6.3 Server Obligations - The server MUST accept all data immediately (or return `Rerror`). It MUST NOT block `Rwrite` waiting for I/O to complete. - The server MUST ensure that data for which `Rwrite` has been sent will survive at least until `Tsync` is responded to. If the server loses buffered data (e.g. due to resource exhaustion) before `Tsync`, it MUST return `Rerror` to the subsequent `Tsync`. - The server MUST handle concurrent `Twrite` messages on the same async fid and apply them in offset order. Out-of-order `Twrite` messages for non-overlapping ranges are permitted; the server MUST apply all of them. ### 6.4 Interaction with Tread A `Tread` on a fid opened `OASYNC` reflects all writes for which `Rwrite` has been returned. It need not reflect writes still in flight (i.e. for which no `Rwrite` has been sent). ### 6.5 Typical Write Pipeline Pattern ``` Topen(fid, OWRITE|OASYNC) → Ropen Twrite(fid, offset=0, data=chunk0) ─┐ Twrite(fid, offset=N, data=chunk1) ├─ all in flight simultaneously Twrite(fid, offset=2N, data=chunk2) ─┘ ← Rwrite(count=N) ← Rwrite(count=N) ← Rwrite(count=N) Tsync(fid) → Rsync ← durable Tclunk(fid) → Rclunk ``` --- ## 7. Pipelining ### 7.1 Requirement In 9P2026, servers MUST support out-of-order responses. A server MUST be able to process multiple in-flight T-messages concurrently and MAY respond to them in any order, using the `tag` field to correlate responses. This is a hard requirement, not an option. Clients may rely on it. ### 7.2 Tag Space Tags are u32. The valid range for client-assigned tags is 0x00000000 through 0xFFFFFFFE. Tag 0xFFFFFFFF is reserved as `NOTAG`. Clients MUST NOT reuse a tag until they have received the corresponding R-message (or `Rflush` for a flushed request). ### 7.3 Ordering Guarantees Despite out-of-order responses being permitted: - A server MUST process `Twrite` messages on the same fid in the order received relative to each other, regardless of response order. (Writes on the same fid are sequenced by arrival.) - `Tread` on the same fid is sequenced with respect to `Twrite` on that fid by arrival order. - Operations on different fids have no ordering guarantees relative to each other. ### 7.4 Head-of-Line Blocking Guidance Clients SHOULD use separate fids for large data transfers and for metadata operations to avoid a large write pipeline stalling small stat requests. The tag system allows the server to interleave responses but servers are not required to preempt in-progress large writes. --- ## 8. Event Notification Convention 9P2026 defines no new messages for filesystem change notification. Instead, servers that wish to expose events SHOULD do so via a synthetic readable file named `events` within any directory that supports notification. This is a convention; clients discover it by attempting to open the file. ### 8.1 The events File - The `events` file appears in directory listings like any other file. - It is opened with `OREAD`. - `Tread` on an open `events` fid blocks until at least one event is available, then returns one or more event records. - `Tread` returning `count == 0` indicates the events stream has closed (e.g. the server is shutting down). - Multiple clients may open the same `events` file; each receives an independent event stream. ### 8.2 Event Record Format Each record returned in a `Tread` response: ``` reclen[2] u16 Byte length of this record (not including reclen itself) evtype[2] u16 Event type (see below) mtime[8] u64 Nanosecond timestamp of the event namelen[2] u16 Byte length of name name[namelen] u8[] Name of affected file (relative to the directory, UTF-8) ``` Records are packed consecutively in the `Rread` data field. Clients consume `reclen` bytes per record. Event types: | Value | Name | Meaning | |---|---|---| | 0x0001 | `EVCREATE` | File or directory created | | 0x0002 | `EVDELETE` | File or directory removed | | 0x0003 | `EVMODIFY` | File data modified | | 0x0004 | `EVATTR` | File metadata modified (stat changed) | | 0x0005 | `EVRENAME` | File renamed within this directory | For `EVRENAME`, a second record with the new name is appended immediately after, with the same `evtype`, `mtime`, and `reclen`. Clients detect this by the pair of consecutive `EVRENAME` records with matching `mtime`. ### 8.3 Multiplexing Events Across Directories Clients that wish to watch multiple directories concurrently SHOULD open each directory's `events` file in a separate process and communicate results over channels, using the `ioproc`/`alt` pattern. No single-fid multiplexing is provided at the protocol level. ### 8.4 Non-Implementing Servers Servers that do not support events MUST return `Rerror` on `Topen` of the `events` file. Servers MUST NOT expose a plain file named `events` that is not an event stream; if a real file with that name would conflict, the server should choose a different synthetic name (e.g. `.events`) and document it. --- ## 9. Compatibility ### 9.1 Version Detection A client that wishes to support both 9P2000 and 9P2026 SHOULD: 1. Send `Tversion` with version `"9P2026"` and the 4-byte tag field. 2. If the server responds with `"9P2026"`, proceed with this specification. 3. If the server responds with `"unknown"`, reconnect and send `Tversion` with version `"9P2000"` and the 2-byte tag field per the original spec. Because `Tversion` uses `NOTAG` (all ones) for its tag, the 2-byte vs 4-byte distinction is unambiguous in the initial handshake: a 9P2026 client sends a 9-byte fixed header (`size[4] type[1] tag[4]`) while a 9P2000 client sends a 7-byte fixed header (`size[4] type[1] tag[2]`). The `size` field disambiguates these in practice. ### 9.2 Extension Fallback Each 9P2026 extension can degrade gracefully: | Feature | Fallback | |---|---| | `Treaddir` | `Tread` directory + `Tstat` per entry | | `Trenegotiate` | Set msize conservatively at session start | | `OASYNC` | Open without `OASYNC`; issue synchronous writes | | `Tsync` | Not needed without `OASYNC` | | `events` file | Poll with `Tstat` | ### 9.3 Servers Implementing a Subset A 9P2026 server MAY decline to implement any extension by returning `Rerror` to the relevant message. Advertising `"9P2026"` in `Rversion` commits the server only to the wire format changes (tag width, timestamp width) and the pipelining mandate. All extension messages are individually optional from the server's perspective, with defined fallback behavior. --- ## 10. Summary of Wire Format Differences from 9P2000 | Field | 9P2000 | 9P2026 | |---|---|---| | `tag` in header | u16 (2 bytes) | u32 (4 bytes) | | `NOTAG` | 0xFFFF | 0xFFFFFFFF | | `atime` in stat | u32 seconds | u64 nanoseconds | | `mtime` in stat | u32 seconds | u64 nanoseconds | | `OASYNC` open flag | not defined | 0x80 | | New message types | — | 128–133 | | Pipelining | implementation-defined | mandatory |