From mboxrd@z Thu Jan 1 00:00:00 1970 Message-ID: To: 9fans@cse.psu.edu Subject: Re: [9fans] Serving 9p in python - anyone started that? From: "Russ Cox" MIME-Version: 1.0 Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: 7bit Date: Wed, 13 Nov 2002 14:18:51 -0500 Topicbox-Message-UUID: 1d95edd6-eacb-11e9-9e20-41e7f4b1d025 yep. here's a minimal 9p2000 test scaffolding. it predates the python dial, hence the weird exec of aux/trampoline in connetwork. all i wrote was version testing. but the fcall module is complete. and the arg parser is nice. # To unbundle, run this file echo 9ptest sed 's/.//' >9ptest <<'//GO.SYSIN DD 9ptest' -#!/bin/python - -from arg import ArgParser -from fcall import * -from plan9 import * -from plan9 import _exec -import plan9, sys - -t,r = None, None -fd = None -Tag = 1 -chatty = 0 - -def chat(s): - if chatty: - sys.stderr.write(s+'\n') - sys.stderr.flush() - -def conservice(name): - return plan9.open(name, plan9.ORDWR) - -def connetwork(addr): - return concommand('aux/trampoline '+addr) - -def concommand(cmd): - p = pipe() - pid = rfork(RFFDG|RFPROC|RFNOWAIT) - if pid == 0: - try: - close(p[0]) - dup(p[1], 0) - dup(p[1], 1) - _exec('/bin/rc', ['rc', '-c', cmd]) - except Exception, msg: - sys.stderr.write('cmd exec: '+str(msg)+'\n') - sys.stderr.flush() - sys.exit(1) - else: - close(p[1]) - return p[0] - -def tx(_t): - global t, r - t = _t - pkt = t.unload() - chat('-> %s' % (t,)) - n = write(fd, pkt) - try: - pkt = read9pmsg(fd) - r = Fcall() - r.load(pkt) - except Exception, e: - sys.stdout.write('sent: '+str(t)+'\n') - sys.stdout.write('error receiving response: '+str(e)+'\n') -# sys.exit(1) - raise - chat('<- %s' % (r,)) - return r - -def note(s): - sys.stdout.write('sent: '+str(t)+'\n') - sys.stdout.write('recv: '+str(r)+'\n') - sys.stdout.write(s+'\n') - sys.stdout.flush() - -def testversion(): - r = tx(mkfcall(Tflush, Tag, 42)) - if r.type != Terror: - note('server should reject messages before first Tversion is sent') - - # test various msizes and versions - def test(msize, vers, expectvers='9P2000'): - r = tx(mkfcall(Tversion, Tag, msize, vers)) - if r.type != Rversion: - note('response to Tversion should always be Rversion') - return - if r.msize > msize: - note('response msize must be <= request msize') - if r.version != expectvers: - note('response version must be '+expectvers) - - test(8192+IOHDRSZ, '9P2000') # common case - test(512, '9P2000') # small msize - test(8192+IOHDRSZ, '9P2005') # future protocol - test(16384, '9P2000') # large msize - test(8192+IOHDRSZ, '9P2000.1') # subprotocol - test(8192+IOHDRSZ, '9P2001.2') # future subprotocol - test(8192+IOHDRSZ, '8P2000', 'unknown') - -def main(): - global fd, chatty - arg = ArgParser('usage: 9ptest [-c|-n] service\n') - con = conservice - for o in arg: - if o=='-c': - con = concommand - elif o=='-n': - con = connetwork - elif o=='-v': - chatty += 1 - else: - arg.error('unknown option '+o) - if len(arg.argv) != 1: - arg.error() - - fd = con(arg.argv[0]) - - testversion() - - -if __name__=='__main__': - main() //GO.SYSIN DD 9ptest echo fcall.py sed 's/.//' >fcall.py <<'//GO.SYSIN DD fcall.py' -"""Pack and unpack 9P2000 messages.""" - -from __future__ import nested_scopes, generators -import sys, struct - -class FcallError(Exception): - def __init__(self, msg=None): - if msg: - self.msg = msg - -IOHDRSZ = 24 - -Tversion, \ -Rversion, \ -Tauth, \ -Rauth, \ -Tattach, \ -Rattach, \ -Terror, \ -Rerror, \ -Tflush, \ -Rflush, \ -Twalk, \ -Rwalk, \ -Topen, \ -Ropen, \ -Tcreate, \ -Rcreate, \ -Tread, \ -Rread, \ -Twrite, \ -Rwrite, \ -Tclunk, \ -Rclunk, \ -Tremove, \ -Rremove, \ -Tstat, \ -Rstat, \ -Twstat, \ -Rwstat = range(100, 128) - -fcalls = { - Tversion: ('LS', ['msize', 'version']), - Rversion: ('LS', ['msize', 'version']), - - Tauth: ('LSS', ['afid', 'uname', 'aname']), - Rauth: ('q', ['aqid']), - - Rerror: ('S', ['ename']), - - Tflush: ('H', ['oldtag']), - Rflush: ('', []), - - Tattach: ('LLSS', ['fid', 'afid', 'uname', 'aname']), - Rattach: ('Q', ['qid']), - - Twalk: ('FF#S', ['fid', 'newfid', 'wname']), - Rwalk: ('#Q', ['qid']), - - Topen: ('LB', ['fid', 'mode']), - Ropen: ('qL', ['qid', 'iounit']), - - Tcreate: ('LSLB', ['fid', 'name', 'perm', 'mode']), - Rcreate: ('qL', ['qid', 'iounit']), - - Tread: ('LQL', ['fid', 'offset', 'count']), - Rread: ('D', ['data']), - - Twrite: ('LQD', ['fid', 'offset', 'data']), - Rwrite: ('L', ['count']), - - Tclunk: ('L', ['fid']), - Rclunk: ('', []), - - Tremove: ('L', ['fid']), - Rremove: ('', []), - - Tstat: ('L', ['fid']), - Rstat: ('D', ['stat']), - - Twstat: ('LD', ['fid', 'stat']), - Rwstat: ('', []) -} - -fcallnames = { - Tversion: 'Tversion', - Rversion: 'Rversion', - - Tauth: 'Tauth', - Rauth: 'Rauth', - - Rerror: 'Rerror', - - Tflush: 'Tflush', - Rflush: 'Rflush', - - Tattach: 'Tattach', - Rattach: 'Rattach', - - Twalk: 'Twalk', - Rwalk: 'Rwalk', - - Topen: 'Topen', - Ropen: 'Ropen', - - Tcreate: 'Tcreate', - Rcreate: 'Rcreate', - - Tread: 'Tread', - Rread: 'Rread', - - Twrite: 'Twrite', - Rwrite: 'Rwrite', - - Tclunk: 'Tclunk', - Rclunk: 'Rclunk', - - Tremove: 'Tremove', - Rremove: 'Rremove', - - Tstat: 'Tstat', - Rstat: 'Rstat', - - Twstat: 'Twstat', - Rwstat: 'Rwstat', -} - -def pack(fmt, vals): - pkt = '' - while fmt: - c = fmt[0] - fmt = fmt[1:] - v = vals[0] - vals = vals[1:] - if c in 'BHLQ': - pkt += struct.pack(c, v) - elif c == 'S': - pkt += pack('H', (len(v),)) - pkt += v - elif c == 'q': - pkt += pack('VLB', v.path, v.vers, v.type) - elif c == 'D': - pkt += pack('L', (len(v),)) - pkt += v - elif c == '#': - c = fmt[0] - fmt = fmt[1:] - pkt += pack(c*len(v), v) - else: - raise Exception('unknown format character \''+c+'\'') - return pkt - -def unpack(fmt, pkt): - l = [] - while fmt: - c = fmt[0] - fmt = fmt[1:] - if c == 'B': - v, = struct.unpack('' - //GO.SYSIN DD fcall.py echo arg.py sed 's/.//' >arg.py <<'//GO.SYSIN DD arg.py' -from __future__ import generators -import sys, copy - -"""Simple command-line argument iterator. - -A typical example looks like: - - arg = ArgParser('usage: example [-d] [-r root] database\n') - for o in arg: - if o=='-d': - debug=1 - elif o=='-r': - root = arg.nextarg() - else: - arg.error('unknown option '+o) - if len(arg.argv) != 1: - arg.error() - -This illustrates all the important points: - - * arg is a generator that returns the next argument. - >>>The behavior of the generator depends on the behavior - of the body of your for loop.<<< This is why you don't have - to tell the ArgParser about your options a priori. If an option - has an argument, you call arg.nextarg() to fetch it. (If you - want an option to have two arguments, call arg.nextarg twice.) - - * After the loop has finished, arg.argv contains the - remaining command-line arguments. - - * Calling arg.error prints the optional passed string, - then prints the usage message, then exits. - -A few other points: - - * Consistent with typical Unix conventions, option parsing ends - after seeing '--', before seeing '-', or before any command-line argument - not beginning with a - (that wasn't gobbled as an option argument). - Assuming that -c does not take an argument but -f does, the following - set of command lines and parsings illustrates the rules: - foo -c -- -a - opts: -c - args: -a - foo -c - -a - opts: -c - args: - -a - foo -c bar baz -a - opts: -c - args: bar baz -a - foo -cfbar baz -a - opts: -c -f - (-f took bar) - args: baz -a - foo -cf bar baz -a - opts: -c -f - (-f took bar) - args: baz -a - foo -fc bar baz -a - opts: -f - (-f took c) - args: bar baz -a - - * Single character short options begin with - and take arguments from the - rest of the current argument or from the next argument. For example, - foo -cbar - foo -c bar - are equivalent. There is no support for optional arguments, since that - introduces command-line parsing ambiguities. - - * Long options begin with -- and take arguments from an optional - ``=ARG'' suffix. For example, - foo --long=bar - is the only way to specify an argument to the --long option. - If the handler for '--long' does not fetch the argument with arg.nextarg, - the parser will call arg.error automatically. There is support for optional - arguments to long options, since that does not introduce any ambiguities. - To fetch an optional argument call arg.nextarg(allownone=1). If there - is no argument, None will be returned. - -""" - -class ArgError(Exception): - def __init__(self, msg=None): - if msg: - self.msg = msg - -class ArgParser: - def __init__(self, usage, argv=sys.argv): - self.argv0 = argv[0] - self.argv = argv[1:] - self.usage = usage - self.waitingarg = '' - - def __iter__(self): - # this assumes the " - while self.argv: - if self.argv[0]=='-' or self.argv[0][0]!='-': - break - a = self.argv.pop(0) - if a=='--': - break - if a[0:2]=='--': - i = a.find('=') - if i==-1: - self.waitingarg=None - self.option = a - yield self.option - self.option = None - else: - self.waitingarg = a[i+1:] - self.option = a[0:i] - yield self.option - if self.waitingarg: # wasn't fetched using optarg - self.error(self.option+' does not take an argument') - self.option = None - continue - self.waitingarg = a[1:] - while self.waitingarg: - a = self.waitingarg[0:1] - self.waitingarg = self.waitingarg[1:] - self.option = '-'+a - yield self.option - self.option = None - - def nextarg(self, allownone=0): - if self.waitingarg==None: - if allownone: - return None - self.error(self.option+' requires an argument') - elif self.waitingarg: - ret = self.waitingarg - self.waitingarg='' - else: - try: - ret = self.argv.pop(0) - except IndexError: - self.error(self.option+' requires an argument') - return ret - - def error(self, msg=None): - if msg: - sys.stderr.write('argument error: '+msg+'\n') - sys.stderr.write(self.usage) - sys.stderr.flush() - sys.exit(1) - //GO.SYSIN DD arg.py