Plan9 9p2000 protocol

The library provides basic asynchronous 9p2000 implementation.

class pyroute2.plan9.server.Plan9ServerSocket(address=None, use_socket=None)

9p2000 server.

Requires either an IP address to listen on, or an open SOCK_STREAM socket to operate. An IP example, suitable to establish IPC between processes in one network:

from pyroute2 import Plan9ClientSocket, Plan9ServerSocket

address = ('localhost', 8149)
p9server = Plan9ServerSocket(address=address)
p9client = Plan9ClientSocket(address=address)

Server/client running on a socketpair() suitable for internal API within one process, or between parent/child processes:

from socket import socketpair

from pyroute2 import Plan9ClientSocket, Plan9ServerSocket

server, client = socketpair()
p9server = Plan9ServerSocket(use_socket=server)
p9client = Plan9ClientSocket(use_socket=client)
register_function(func, inode, loader=<function loads>, dumper=<function Plan9ServerSocket.<lambda>>)

Register a function to an file.

The file usage:

  • write(): write arguments for the call as a json dictionary of keyword arguments to the file data.

  • read():
    1. if the arguments were written to the data, call the function and write the result to the file data

    2. read the file data and return to the client

  • call(): protocol extension, Tcall = 80, Rcall = 81, make this in one turn.

Registering a function:

def communicate(a, b):
    return a + b


def example_register():
    fd = p9server.filesystem.create('test_func')
    p9server.register_function(communicate, fd)

Communication using Twrite/Tread:

import json


async def example_write():
    fid = await p9client.fid('test_func')
    await p9client.write(
        fid,
        json.dumps({"a": 17, "b": 25})
    )
    msg = await p9client.read(fid)
    response = json.loads(msg['data'])
    assert response == 42

Same, using a command line 9p client from plan9port:

$ echo '{"a": 17, "b": 25}' | 9p -a localhost:8149 write test_func
$ 9p -a localhost:8149 read test_func
42

And using a mounted file system via FUSE client from plan9port:

$ 9pfuse localhost:8149 mnt
$ echo '{"a": 17, "b": 25}' >mnt/test_func
$ cat mnt/test_func
42

And the same, but using Tcall:

async def example_call():
    fid = await p9client.fid('test_func')
    response = await p9client.call(fid, argv=(17, 25))
    assert response == 42

And finnaly run this code:

async def main():
    server_task = await p9server.async_run()
    example_register()
    await p9client.start_session()
    await example_write()
    await example_call()
    server_task.cancel()

asyncio.run(main())
async async_run()

Return the server asyncio task.

Using this task one can stop the server:

async def main():
    server = Plan9ServerSocket(address=('localhost', 8149))
    server_task = await server.async_run()
    # ... server is running here
    server_task.cancel()
    # ... server is stopped

asyncio.run(main())

To forcefully close all client connections and stop the server immediately from a registered function, one can pass this task to the function, cancel it, and raise Plan9Exit() exception:

import functools

from pyroute2.plan9 import Plan9Exit

server_sock, client_sock = socketpair()


def test_exit_func(context):
    if 'server_task' in context:
        context['server_task'].cancel()
        raise Plan9Exit('server stopped upon client request')
    return 'server starting, please wait'


async def server():
    p9server = Plan9ServerSocket(use_socket=server_sock)
    context = {}

    inode = p9server.filesystem.create('stop')
    p9server.register_function(
        functools.partial(test_exit_func, context),
        inode
    )
    context['server_task'] = await p9server.async_run()

    try:
        await context['server_task']
    except asyncio.exceptions.CancelledError:
        pass

    assert context['server_task'].cancelled()
run()

A simple synchronous runner.

Uses event_loop.run_forever().

class pyroute2.plan9.client.Plan9ClientSocket(address=None, use_socket=None)

9p2000 client.

  • address -- ('address', port) to listen on

  • use_socket -- alternatively, provide a connected SOCK_STRAM socket

async start_session()

Initiate 9p2000 session.

One must await this routine before running any other requests.

async version()

Tverion request. No arguments required.

async attach(aname='')

Tattach request.

  • aname (optional) -- aname to attach to

async walk(path, newfid=None, fid=None)

Twalk request.

  • path -- string path to the file

  • newfid (optional) -- use this fid to store the info

  • fid (optional) -- use this fid to walk from, otherwise walk from the current directory for this client session

async fid(path)

Walk the path and return fid to the required file.

  • path -- string path to the file

async read(fid, offset=0, count=8192)

Tread request.

  • fid -- fid of the file to read from

  • offset (optional, default 0) -- read offset

  • count (optional, default 8192) -- read count

async write(fid, data, offset=0)

Twrite request.

  • fid -- fid of the file to write to

  • data -- bytes to write

  • offset (optional, default 0) -- write offset

async call(fid, argv=None, kwarg=None, data=b'', data_arg='data', loader=<function loads>)

Tcall request.

  • fid -- fid of the file that represents a registered function

  • argv (optional) -- positional arguments as an iterable

  • kwarg (optional) -- keyword arguments as a dictionary

  • data (opional) -- optional binary data

  • data_arg (optional) -- name of the argument to use with the binary data

  • loader (optional, default json.loads) -- loader for the response data