Skip to content

Commit

Permalink
docs: basic plan9 9p2000 server & client
Browse files Browse the repository at this point in the history
+ doctest
  • Loading branch information
svinota committed Jan 20, 2025
1 parent 4d246f4 commit 35d254e
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Usage
asyncio
iproute
ndb
plan9
wiset
ipset
netns
Expand Down
20 changes: 20 additions & 0 deletions docs/plan9.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.. _plan9:

.. testsetup::

import asyncio

from pyroute2.plan9.server import Plan9ServerSocket
from pyroute2.plan9.client import Plan9ClientSocket


Plan9 9p2000 protocol
=====================

The library provides basic asynchronous 9p2000 implementation.

.. autoclass:: pyroute2.plan9.server.Plan9ServerSocket
:members:

.. autoclass:: pyroute2.plan9.client.Plan9ClientSocket
:members:
4 changes: 4 additions & 0 deletions pyroute2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
from pyroute2.netlink.taskstats import TaskStats
from pyroute2.netlink.uevent import UeventSocket
from pyroute2.nslink.nspopen import NSPopen
from pyroute2.plan9.client import Plan9ClientSocket
from pyroute2.plan9.server import Plan9ServerSocket
from pyroute2.wiset import WiSet

modules = [
Expand Down Expand Up @@ -91,6 +93,8 @@
NFTSocket,
NL80211,
NSPopen,
Plan9ClientSocket,
Plan9ServerSocket,
ProcEventSocket,
RawIPRoute,
Server,
Expand Down
2 changes: 2 additions & 0 deletions pyroute2/plan9/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,8 @@ def parse(self, data, seq=None, callback=None, skip_alien_seq=False):
spec = json.loads(msg['ename'])
if spec['class'] in dir(builtins):
cls = getattr(builtins, spec['class'])
elif spec['class'] == 'Plan9Exit':
cls = Plan9Exit
else:
cls = Exception
if not spec.get('argv'):
Expand Down
73 changes: 64 additions & 9 deletions pyroute2/plan9/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@


class Plan9ClientSocket(AsyncCoreSocket):
'''9p2000 client.
* address -- `('address', port)` to listen on
* use_socket -- alternatively, provide a connected SOCK_STRAM socket
'''

def __init__(self, address=None, use_socket=None):
self.spec = CoreSocketSpec(
{
Expand Down Expand Up @@ -60,6 +66,10 @@ async def setup_endpoint(self, loop=None):
)

async def start_session(self):
'''Initiate 9p2000 session.
One must await this routine before running any other requests.
'''
await self.ensure_socket()
await self.version()
await self.auth()
Expand All @@ -80,6 +90,7 @@ async def request(self, msg, tag=0):
self.addr_pool.free(tag, ban=0xFF)

async def version(self):
'''`Tverion` request. No arguments required.'''
m = msg_tversion()
m['header']['tag'] = 0xFFFF
m['msize'] = 8192
Expand All @@ -89,15 +100,26 @@ async def version(self):
async def auth(self):
pass

async def attach(self):
async def attach(self, aname=''):
'''`Tattach` request.
* `aname` (optional) -- aname to attach to
'''
m = msg_tattach()
m['fid'] = 0
m['afid'] = 0xFFFFFFFF
m['uname'] = pwd.getpwuid(os.getuid()).pw_name
m['aname'] = ''
m['aname'] = aname
return await self.request(m)

async def walk(self, 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
'''
m = msg_twalk()
m['fid'] = self.cwd if fid is None else fid
m['newfid'] = newfid if newfid is not None else self.fid_pool.alloc()
Expand All @@ -106,31 +128,63 @@ async def walk(self, path, newfid=None, fid=None):
return await self.request(m)

async def fid(self, path):
'''Walk the path and return `fid` to the required file.
* `path` -- string path to the file
'''
if path not in self.wnames:
newfid = self.fid_pool.alloc()
await self.walk(path, newfid)
self.wnames[path] = newfid
return self.wnames[path]

async def read(self, fid):
async def read(self, 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
'''
m = msg_tread()
m['fid'] = fid
m['offset'] = 0
m['count'] = 8192
m['offset'] = offset
m['count'] = count
return await self.request(m)

async def write(self, fid, data):
async def write(self, fid, data, offset=0):
'''`Twrite` request.
* `fid` -- fid of the file to write to
* `data` -- bytes to write
* `offset` (optional, default 0) -- write offset
'''
m = msg_twrite()
m['fid'] = fid
m['offset'] = 0
m['data'] = data
return await self.request(m)

async def call(
self, fid, fname='', argv=None, kwarg=None, data=b'', data_arg='data'
self,
fid,
argv=None,
kwarg=None,
data=b'',
data_arg='data',
loader=json.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
'''
spec = {
'call': fname,
'argv': argv if argv is not None else [],
'kwarg': kwarg if kwarg is not None else {},
'data_arg': data_arg,
Expand All @@ -139,4 +193,5 @@ async def call(
m['fid'] = fid
m['text'] = json.dumps(spec)
m['data'] = data
return await self.request(m)
response = await self.request(m)
return loader(response['data'])
4 changes: 2 additions & 2 deletions pyroute2/plan9/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def _publish_function_r(
inode.data.write(dumper(ret))

inode.data.seek(request['offset'])
response['data'] = dumper(inode.data.read(request['count']))
response['data'] = inode.data.read(request['count'])
return response


Expand Down Expand Up @@ -120,7 +120,7 @@ def get_child(self, name):
return child
raise KeyError('file not found')

def publish_function(
def register_function(
self,
func,
loader=json.loads,
Expand Down
Loading

0 comments on commit 35d254e

Please sign in to comment.