xref: /freebsd-src/contrib/lib9p/pytest/protocol.py (revision 134e17798c9af53632b372348ab828e75e65bf46)
1*134e1779SJakub Wojciech Klama#! /usr/bin/env python
2*134e1779SJakub Wojciech Klama
3*134e1779SJakub Wojciech Klama"""
4*134e1779SJakub Wojciech KlamaProtocol definitions for python based lib9p server/client.
5*134e1779SJakub Wojciech Klama
6*134e1779SJakub Wojciech KlamaThe sub-namespace td has type definitions (qid, stat) and values
7*134e1779SJakub Wojciech Klamathat are "#define" constants in C code (e.g., DMDIR, QTFILE, etc).
8*134e1779SJakub Wojciech KlamaThis also contains the byte values for protocol codes like Tversion,
9*134e1779SJakub Wojciech KlamaRversion, Rerror, and so on.
10*134e1779SJakub Wojciech Klama
11*134e1779SJakub Wojciech Klama    >>> td.Tversion
12*134e1779SJakub Wojciech Klama    100
13*134e1779SJakub Wojciech Klama    >>> td.Rlerror
14*134e1779SJakub Wojciech Klama    7
15*134e1779SJakub Wojciech Klama
16*134e1779SJakub Wojciech KlamaThe qid and stat types are PFOD classes and generate instances that
17*134e1779SJakub Wojciech Klamaare a cross between namedtuple and OrderedDictionary (see pfod.py
18*134e1779SJakub Wojciech Klamafor details):
19*134e1779SJakub Wojciech Klama
20*134e1779SJakub Wojciech Klama    >>> td.qid(type=td.QTFILE, path=2, version=1)
21*134e1779SJakub Wojciech Klama    qid(type=0, version=1, path=2)
22*134e1779SJakub Wojciech Klama
23*134e1779SJakub Wojciech KlamaThe td.stat() type output is pretty long, since it has all the
24*134e1779SJakub Wojciech Klamadotu-specific members (used only when packing for dotu/dotl and
25*134e1779SJakub Wojciech Klamaset only when unpacking those), so here's just one field:
26*134e1779SJakub Wojciech Klama
27*134e1779SJakub Wojciech Klama    >>> td.stat(*(15 * [0])).mode
28*134e1779SJakub Wojciech Klama    0
29*134e1779SJakub Wojciech Klama    >>> import pprint; pprint.pprint(td.stat()._fields)
30*134e1779SJakub Wojciech Klama    ('type',
31*134e1779SJakub Wojciech Klama     'dev',
32*134e1779SJakub Wojciech Klama     'qid',
33*134e1779SJakub Wojciech Klama     'mode',
34*134e1779SJakub Wojciech Klama     'atime',
35*134e1779SJakub Wojciech Klama     'mtime',
36*134e1779SJakub Wojciech Klama     'length',
37*134e1779SJakub Wojciech Klama     'name',
38*134e1779SJakub Wojciech Klama     'uid',
39*134e1779SJakub Wojciech Klama     'gid',
40*134e1779SJakub Wojciech Klama     'muid',
41*134e1779SJakub Wojciech Klama     'extension',
42*134e1779SJakub Wojciech Klama     'n_uid',
43*134e1779SJakub Wojciech Klama     'n_gid',
44*134e1779SJakub Wojciech Klama     'n_muid')
45*134e1779SJakub Wojciech Klama
46*134e1779SJakub Wojciech KlamaStat objects sent across the protocol must first be encoded into
47*134e1779SJakub Wojciech Klamawirestat objects, which are basically size-counted pre-sequenced
48*134e1779SJakub Wojciech Klamastat objects.  The pre-sequencing uses:
49*134e1779SJakub Wojciech Klama
50*134e1779SJakub Wojciech Klama    >>> td.stat_seq
51*134e1779SJakub Wojciech Klama    Sequencer('stat')
52*134e1779SJakub Wojciech Klama
53*134e1779SJakub Wojciech KlamaFor parsing bytes returned in a Tread on a directory, td.wirestat_seq
54*134e1779SJakub Wojciech Klamais the sequencer.  However, most users should rely on the packers and
55*134e1779SJakub Wojciech Klamaunpackers in each protocol (see {pack,unpack}_wirestat below).
56*134e1779SJakub Wojciech Klama
57*134e1779SJakub Wojciech Klama    >>> td.wirestat_seq
58*134e1779SJakub Wojciech Klama    Sequencer('wirestat')
59*134e1779SJakub Wojciech Klama
60*134e1779SJakub Wojciech KlamaThere is a dictionary fcall_to_name that maps from byte value
61*134e1779SJakub Wojciech Klamato protocol code.  Names map to themselves as well:
62*134e1779SJakub Wojciech Klama
63*134e1779SJakub Wojciech Klama    >>> fcall_names[101]
64*134e1779SJakub Wojciech Klama    'Rversion'
65*134e1779SJakub Wojciech Klama    >>> fcall_names['Tversion']
66*134e1779SJakub Wojciech Klama    'Tversion'
67*134e1779SJakub Wojciech Klama
68*134e1779SJakub Wojciech KlamaThe sub-namespace rrd has request (Tversion, Topen, etc) and
69*134e1779SJakub Wojciech Klamaresponse (Rversion, Ropen, etc) data definitions.  Each of these
70*134e1779SJakub Wojciech Klamais a PFOD class:
71*134e1779SJakub Wojciech Klama
72*134e1779SJakub Wojciech Klama    >>> rrd.Tversion(1000, 'hello', tag=0)
73*134e1779SJakub Wojciech Klama    Tversion(tag=0, msize=1000, version='hello')
74*134e1779SJakub Wojciech Klama
75*134e1779SJakub Wojciech KlamaThe function p9_version() looks up the instance of each supported
76*134e1779SJakub Wojciech Klamaprotocol, or raises a KeyError when given an invalid protocol.
77*134e1779SJakub Wojciech KlamaThe names may be spelled in any mixture of cases.
78*134e1779SJakub Wojciech Klama
79*134e1779SJakub Wojciech KlamaThe names plain, dotu, and dotl are predefined as the three
80*134e1779SJakub Wojciech Klamasupported protocols:
81*134e1779SJakub Wojciech Klama
82*134e1779SJakub Wojciech Klama    >>> p9_version('invalid')
83*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
84*134e1779SJakub Wojciech Klama        ...
85*134e1779SJakub Wojciech Klama    KeyError: 'invalid'
86*134e1779SJakub Wojciech Klama    >>> p9_version('9p2000') == plain
87*134e1779SJakub Wojciech Klama    True
88*134e1779SJakub Wojciech Klama    >>> p9_version('9P2000') == plain
89*134e1779SJakub Wojciech Klama    True
90*134e1779SJakub Wojciech Klama    >>> p9_version('9P2000.u') == dotu
91*134e1779SJakub Wojciech Klama    True
92*134e1779SJakub Wojciech Klama    >>> p9_version('9p2000.L') == dotl
93*134e1779SJakub Wojciech Klama    True
94*134e1779SJakub Wojciech Klama
95*134e1779SJakub Wojciech KlamaProtocol instances have a pack() method that encodes a set of
96*134e1779SJakub Wojciech Klamaarguments into a packet.  To know what to encode, pack() must
97*134e1779SJakub Wojciech Klamareceive an fcall value and a dictionary containing argument
98*134e1779SJakub Wojciech Klamavalues, or something equivalent.  The required argument values
99*134e1779SJakub Wojciech Klamadepend on the fcall.  For instance, a Tversion fcall needs three
100*134e1779SJakub Wojciech Klamaarguments: the version name, the tag, and the msize (these of
101*134e1779SJakub Wojciech Klamacourse are the pre-filled fields in a Tversion PFOD instance).
102*134e1779SJakub Wojciech Klama
103*134e1779SJakub Wojciech Klama    >>> args = {'version': '!', 'tag': 1, 'msize': 1000}
104*134e1779SJakub Wojciech Klama    >>> pkt = dotu.pack(fcall='Tversion', args=args)
105*134e1779SJakub Wojciech Klama    >>> len(pkt)
106*134e1779SJakub Wojciech Klama    14
107*134e1779SJakub Wojciech Klama
108*134e1779SJakub Wojciech KlamaThe length of string '!' is 1, and the packet (or wire) format of
109*134e1779SJakub Wojciech Klamaa Tversion request is:
110*134e1779SJakub Wojciech Klama
111*134e1779SJakub Wojciech Klama   size[4] fcall[1] tag[2] msize[4] version[s]
112*134e1779SJakub Wojciech Klama
113*134e1779SJakub Wojciech Klamawhich corresponds to a struct's IBHIH (for the fixed size parts)
114*134e1779SJakub Wojciech Klamafollowed by 1 B (for the string).  The overall packet is 14 bytes
115*134e1779SJakub Wojciech Klamalong, so we have size=9, fcall=100, tag=1, msize=1000, and the
116*134e1779SJakub Wojciech Klamaversion string is length=1, value=33 (ord('!')).
117*134e1779SJakub Wojciech Klama
118*134e1779SJakub Wojciech Klama    >>> import struct
119*134e1779SJakub Wojciech Klama    >>> struct.unpack('<IBHIHB', pkt)
120*134e1779SJakub Wojciech Klama    (14, 100, 1, 1000, 1, 33)
121*134e1779SJakub Wojciech Klama
122*134e1779SJakub Wojciech KlamaOf course, this packed a completely bogus "version" string, but
123*134e1779SJakub Wojciech Klamathat's what we told it to do.  Protocol instances remember their
124*134e1779SJakub Wojciech Klamaversion, so we can get it right by omitting the version from the
125*134e1779SJakub Wojciech Klamaarguments:
126*134e1779SJakub Wojciech Klama
127*134e1779SJakub Wojciech Klama    >>> dotu.version
128*134e1779SJakub Wojciech Klama    '9P2000.u'
129*134e1779SJakub Wojciech Klama    >>> args = {'tag': 99, 'msize': 1000}
130*134e1779SJakub Wojciech Klama    >>> pkt = dotu.pack(fcall='Tversion', args=args)
131*134e1779SJakub Wojciech Klama    >>> len(pkt)
132*134e1779SJakub Wojciech Klama    21
133*134e1779SJakub Wojciech Klama
134*134e1779SJakub Wojciech KlamaThe fcall can be supplied numerically:
135*134e1779SJakub Wojciech Klama
136*134e1779SJakub Wojciech Klama    >>> pkt2 = dotu.pack(fcall=td.Tversion, args=args)
137*134e1779SJakub Wojciech Klama    >>> pkt == pkt2
138*134e1779SJakub Wojciech Klama    True
139*134e1779SJakub Wojciech Klama
140*134e1779SJakub Wojciech KlamaInstead of providing an fcall you can provide an instance of
141*134e1779SJakub Wojciech Klamathe appropriate PFOD.  In this case pack() finds the type from
142*134e1779SJakub Wojciech Klamathe PFOD instance.  As usual, the version parameter is filled in
143*134e1779SJakub Wojciech Klamafor you:
144*134e1779SJakub Wojciech Klama
145*134e1779SJakub Wojciech Klama    >>> pkt2 = dotu.pack(rrd.Tversion(tag=99, msize=1000))
146*134e1779SJakub Wojciech Klama    >>> pkt == pkt2
147*134e1779SJakub Wojciech Klama    True
148*134e1779SJakub Wojciech Klama
149*134e1779SJakub Wojciech KlamaNote that it's up to you to check the other end's version and
150*134e1779SJakub Wojciech Klamaswitch to a "lower" protocol as needed.  Each instance does provide
151*134e1779SJakub Wojciech Klamaa downgrade_to() method that gets you a possibly-downgraded instance.
152*134e1779SJakub Wojciech KlamaThis will fail if you are actually trying to upgrade, and also if
153*134e1779SJakub Wojciech Klamayou provide a bogus version:
154*134e1779SJakub Wojciech Klama
155*134e1779SJakub Wojciech Klama    >>> dotu.downgrade_to('9P2000.L')
156*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
157*134e1779SJakub Wojciech Klama        ...
158*134e1779SJakub Wojciech Klama    KeyError: '9P2000.L'
159*134e1779SJakub Wojciech Klama    >>> dotu.downgrade_to('we never heard of this protocol')
160*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
161*134e1779SJakub Wojciech Klama        ...
162*134e1779SJakub Wojciech Klama    KeyError: 'we never heard of this protocol'
163*134e1779SJakub Wojciech Klama
164*134e1779SJakub Wojciech KlamaHence you might use:
165*134e1779SJakub Wojciech Klama
166*134e1779SJakub Wojciech Klama    try:
167*134e1779SJakub Wojciech Klama        proto = protocol.dotl.downgrade(vstr)
168*134e1779SJakub Wojciech Klama    except KeyError:
169*134e1779SJakub Wojciech Klama        pkt = protocol.plain.pack(fcall='Rerror',
170*134e1779SJakub Wojciech Klama            args={'tag': tag, 'errstr': 'unknown protocol version '
171*134e1779SJakub Wojciech Klama                    '{0!r}'.format(vstr)})
172*134e1779SJakub Wojciech Klama    else:
173*134e1779SJakub Wojciech Klama        pkt = proto.pack(fcall='Rversion', args={'tag': tag, 'msize': msize})
174*134e1779SJakub Wojciech Klama
175*134e1779SJakub Wojciech KlamaWhen using a PFOD instance, it is slightly more efficient to use
176*134e1779SJakub Wojciech Klamapack_from():
177*134e1779SJakub Wojciech Klama
178*134e1779SJakub Wojciech Klama    try:
179*134e1779SJakub Wojciech Klama        proto = protocol.dotl.downgrade(vstr)
180*134e1779SJakub Wojciech Klama        reply = protocol.rrd.Rversion(tag=tag, msize=msize)
181*134e1779SJakub Wojciech Klama    except KeyError:
182*134e1779SJakub Wojciech Klama        proto = protocol.plain
183*134e1779SJakub Wojciech Klama        reply = protocol.rrd.Rerror(tag=tag,
184*134e1779SJakub Wojciech Klama            errstr='unknown protocol version {0!r}'.format(vstr))
185*134e1779SJakub Wojciech Klama    pkt = proto.pack_from(reply)
186*134e1779SJakub Wojciech Klama
187*134e1779SJakub Wojciech Klamadoes the equivalent of the try/except/else variant.  Note that
188*134e1779SJakub Wojciech Klamathe protocol.rrd.Rversion() instance has version=None.  Like
189*134e1779SJakub Wojciech Klamaproto.pack, the pack_from will detect this "missing" value and
190*134e1779SJakub Wojciech Klamafill it in.
191*134e1779SJakub Wojciech Klama
192*134e1779SJakub Wojciech KlamaBecause errors vary (one should use Rlerror for dotl and Rerror
193*134e1779SJakub Wojciech Klamafor dotu and plain), and it's convenient to use an Exception
194*134e1779SJakub Wojciech Klamainstance for an error, all protocols provide .error().  This
195*134e1779SJakub Wojciech Klamabuilds the appropriate kind of error response, extracting and
196*134e1779SJakub Wojciech Klamaconverting errno's and error messages as appropriate.
197*134e1779SJakub Wojciech Klama
198*134e1779SJakub Wojciech KlamaIf <err> is an instance of Exception, err.errno provides the errnum
199*134e1779SJakub Wojciech Klamaor ecode value (if used, for dotu and dotl) and err.strerror as the
200*134e1779SJakub Wojciech Klamaerrstr value (if used, for plain 9p2000).  Otherwise err should be
201*134e1779SJakub Wojciech Klamaan integer, and we'll use os.strerror() to get a message.
202*134e1779SJakub Wojciech Klama
203*134e1779SJakub Wojciech KlamaWhen using plain 9P2000 this sends error *messages*:
204*134e1779SJakub Wojciech Klama
205*134e1779SJakub Wojciech Klama    >>> import errno, os
206*134e1779SJakub Wojciech Klama    >>> utf8 = os.strerror(errno.ENOENT).encode('utf-8')
207*134e1779SJakub Wojciech Klama    >>> pkt = None
208*134e1779SJakub Wojciech Klama    >>> try:
209*134e1779SJakub Wojciech Klama    ...     os.open('presumably this file does not exist here', 0)
210*134e1779SJakub Wojciech Klama    ... except OSError as err:
211*134e1779SJakub Wojciech Klama    ...     pkt = plain.error(1, err)
212*134e1779SJakub Wojciech Klama    ...
213*134e1779SJakub Wojciech Klama    >>> pkt[-len(utf8):] == utf8
214*134e1779SJakub Wojciech Klama    True
215*134e1779SJakub Wojciech Klama    >>> pkt2 = plain.error(1, errno.ENOENT)
216*134e1779SJakub Wojciech Klama    >>> pkt == pkt2
217*134e1779SJakub Wojciech Klama    True
218*134e1779SJakub Wojciech Klama
219*134e1779SJakub Wojciech KlamaWhen using 9P2000.u it sends the error code as well, and when
220*134e1779SJakub Wojciech Klamausing 9P2000.L it sends only the error code (and more error
221*134e1779SJakub Wojciech Klamacodes can pass through):
222*134e1779SJakub Wojciech Klama
223*134e1779SJakub Wojciech Klama    >>> len(pkt)
224*134e1779SJakub Wojciech Klama    34
225*134e1779SJakub Wojciech Klama    >>> len(dotu.error(1, errno.ENOENT))
226*134e1779SJakub Wojciech Klama    38
227*134e1779SJakub Wojciech Klama    >>> len(dotl.error(1, errno.ENOENT))
228*134e1779SJakub Wojciech Klama    11
229*134e1779SJakub Wojciech Klama
230*134e1779SJakub Wojciech KlamaFor even more convenience (and another slight speed hack), the
231*134e1779SJakub Wojciech Klamaprotocol has member functions for each valid pfod, which
232*134e1779SJakub Wojciech Klamaeffectively do a pack_from of a pfod built from the arguments.  In
233*134e1779SJakub Wojciech Klamathe above example this is not very useful (because we want two
234*134e1779SJakub Wojciech Klamadifferent replies), but for Rlink, for instance, which has only
235*134e1779SJakub Wojciech Klamaa tag, a server might implement Tlink() as:
236*134e1779SJakub Wojciech Klama
237*134e1779SJakub Wojciech Klama    def do_Tlink(proto, data): # data will be a protocol.rrd.Tlink(...)
238*134e1779SJakub Wojciech Klama        tag = data.tag
239*134e1779SJakub Wojciech Klama        dfid = data.dfid
240*134e1779SJakub Wojciech Klama        fid = data.fid
241*134e1779SJakub Wojciech Klama        name = data.name
242*134e1779SJakub Wojciech Klama        ... some code to set up for doing the link link ...
243*134e1779SJakub Wojciech Klama        try:
244*134e1779SJakub Wojciech Klama            os.link(path1, path2)
245*134e1779SJakub Wojciech Klama        except OSError as err:
246*134e1779SJakub Wojciech Klama            return proto.error(tag, err)
247*134e1779SJakub Wojciech Klama        else:
248*134e1779SJakub Wojciech Klama            return proto.Rlink(tag)
249*134e1779SJakub Wojciech Klama
250*134e1779SJakub Wojciech Klama    >>> pkt = dotl.Rlink(12345)
251*134e1779SJakub Wojciech Klama    >>> struct.unpack('<IBH', pkt)
252*134e1779SJakub Wojciech Klama    (7, 71, 12345)
253*134e1779SJakub Wojciech Klama
254*134e1779SJakub Wojciech KlamaSimilarly, a client can build a Tversion packet quite trivially:
255*134e1779SJakub Wojciech Klama
256*134e1779SJakub Wojciech Klama    >>> vpkt = dotl.Tversion(tag=0, msize=12345)
257*134e1779SJakub Wojciech Klama
258*134e1779SJakub Wojciech KlamaTo see that this is a valid version packet, let's unpack its bytes.
259*134e1779SJakub Wojciech KlamaThe overall length is 21 bytes: 4 bytes of size, 1 byte of code 100
260*134e1779SJakub Wojciech Klamafor Tversion, 2 bytes of tag, 4 bytes of msize, 2 bytes of string
261*134e1779SJakub Wojciech Klamalength, and 8 bytes of string '9P2000.L'.
262*134e1779SJakub Wojciech Klama
263*134e1779SJakub Wojciech Klama    >>> tup = struct.unpack('<IBHIH8B', vpkt)
264*134e1779SJakub Wojciech Klama    >>> tup[0:5]
265*134e1779SJakub Wojciech Klama    (21, 100, 0, 12345, 8)
266*134e1779SJakub Wojciech Klama    >>> ''.join(chr(i) for i in tup[5:])
267*134e1779SJakub Wojciech Klama    '9P2000.L'
268*134e1779SJakub Wojciech Klama
269*134e1779SJakub Wojciech KlamaOf course, since you can *pack*, you can also *unpack*.  It's
270*134e1779SJakub Wojciech Klamapossible that the incoming packet is malformed.  If so, this
271*134e1779SJakub Wojciech Klamaraises various errors (see below).
272*134e1779SJakub Wojciech Klama
273*134e1779SJakub Wojciech KlamaUnpack is actually a two step process: first we unpack a header
274*134e1779SJakub Wojciech Klama(where the size is already removed and is implied by len(data)),
275*134e1779SJakub Wojciech Klamathen we unpack the data within the packet.  You can invoke the
276*134e1779SJakub Wojciech Klamafirst step separately.  Furthermore, there's a noerror argument
277*134e1779SJakub Wojciech Klamathat leaves some fields set to None or empty strings, if the
278*134e1779SJakub Wojciech Klamapacket is too short.  (Note that we need a hack for py2k vs py3k
279*134e1779SJakub Wojciech Klamastrings here, for doctests.  Also, encoding 12345 into a byte
280*134e1779SJakub Wojciech Klamastring produces '90', by ASCII luck!)
281*134e1779SJakub Wojciech Klama
282*134e1779SJakub Wojciech Klama    >>> pkt = pkt[4:] # strip generated size
283*134e1779SJakub Wojciech Klama    >>> import sys
284*134e1779SJakub Wojciech Klama    >>> py3k = sys.version_info[0] >= 3
285*134e1779SJakub Wojciech Klama    >>> b2s = lambda x: x.decode('utf-8') if py3k else x
286*134e1779SJakub Wojciech Klama    >>> d = plain.unpack_header(pkt[0:1], noerror=True)
287*134e1779SJakub Wojciech Klama    >>> d.data = b2s(d.data)
288*134e1779SJakub Wojciech Klama    >>> d
289*134e1779SJakub Wojciech Klama    Header(size=5, dsize=0, fcall=71, data='')
290*134e1779SJakub Wojciech Klama    >>> d = plain.unpack_header(pkt[0:2], noerror=True)
291*134e1779SJakub Wojciech Klama    >>> d.data = b2s(d.data)
292*134e1779SJakub Wojciech Klama    >>> d
293*134e1779SJakub Wojciech Klama    Header(size=6, dsize=1, fcall=71, data='9')
294*134e1779SJakub Wojciech Klama
295*134e1779SJakub Wojciech KlamaWithout noerror=True a short packet raises a SequenceError:
296*134e1779SJakub Wojciech Klama
297*134e1779SJakub Wojciech Klama    >>> plain.unpack_header(pkt[0:0])   # doctest: +IGNORE_EXCEPTION_DETAIL
298*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
299*134e1779SJakub Wojciech Klama        ...
300*134e1779SJakub Wojciech Klama    SequenceError: out of data while unpacking 'fcall'
301*134e1779SJakub Wojciech Klama
302*134e1779SJakub Wojciech KlamaOf course, a normal packet decodes fine:
303*134e1779SJakub Wojciech Klama
304*134e1779SJakub Wojciech Klama    >>> d = plain.unpack_header(pkt)
305*134e1779SJakub Wojciech Klama    >>> d.data = b2s(d.data)
306*134e1779SJakub Wojciech Klama    >>> d
307*134e1779SJakub Wojciech Klama    Header(size=7, dsize=2, fcall=71, data='90')
308*134e1779SJakub Wojciech Klama
309*134e1779SJakub Wojciech Klamabut one that is too *long* potentially raises a SequencError.
310*134e1779SJakub Wojciech Klama(This is impossible for a header, though, since the size and
311*134e1779SJakub Wojciech Klamadata size are both implied: either there is an fcall code, and
312*134e1779SJakub Wojciech Klamathe rest of the bytes are "data", or there isn't and the packet
313*134e1779SJakub Wojciech Klamais too short.  So we can only demonstrate this for regular
314*134e1779SJakub Wojciech Klamaunpack; see below.)
315*134e1779SJakub Wojciech Klama
316*134e1779SJakub Wojciech KlamaNote that all along, this has been decoding Rlink (fcall=71),
317*134e1779SJakub Wojciech Klamawhich is not valid for plain 9P2000 protocol.  It's up to the
318*134e1779SJakub Wojciech Klamacaller to check:
319*134e1779SJakub Wojciech Klama
320*134e1779SJakub Wojciech Klama    >>> plain.supports(71)
321*134e1779SJakub Wojciech Klama    False
322*134e1779SJakub Wojciech Klama
323*134e1779SJakub Wojciech Klama    >>> plain.unpack(pkt)           # doctest: +IGNORE_EXCEPTION_DETAIL
324*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
325*134e1779SJakub Wojciech Klama        ...
326*134e1779SJakub Wojciech Klama    SequenceError: invalid fcall 'Rlink' for 9P2000
327*134e1779SJakub Wojciech Klama    >>> dotl.unpack(pkt)
328*134e1779SJakub Wojciech Klama    Rlink(tag=12345)
329*134e1779SJakub Wojciech Klama
330*134e1779SJakub Wojciech KlamaHowever, the unpack() method DOES check that the fcall type is
331*134e1779SJakub Wojciech Klamavalid, even if you supply noerror=True.  This is because we can
332*134e1779SJakub Wojciech Klamaonly really decode the header, not the data, if the fcall is
333*134e1779SJakub Wojciech Klamainvalid:
334*134e1779SJakub Wojciech Klama
335*134e1779SJakub Wojciech Klama    >>> plain.unpack(pkt, noerror=True)     # doctest: +IGNORE_EXCEPTION_DETAIL
336*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
337*134e1779SJakub Wojciech Klama        ...
338*134e1779SJakub Wojciech Klama    SequenceError: invalid fcall 'Rlink' for 9P2000
339*134e1779SJakub Wojciech Klama
340*134e1779SJakub Wojciech KlamaThe same applies to much-too-short packets even if noerror is set.
341*134e1779SJakub Wojciech KlamaSpecifically, if the (post-"size") header shortens down to the empty
342*134e1779SJakub Wojciech Klamastring, the fcall will be None:
343*134e1779SJakub Wojciech Klama
344*134e1779SJakub Wojciech Klama    >>> dotl.unpack(b'', noerror=True)      # doctest: +IGNORE_EXCEPTION_DETAIL
345*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
346*134e1779SJakub Wojciech Klama        ...
347*134e1779SJakub Wojciech Klama    SequenceError: invalid fcall None for 9P2000.L
348*134e1779SJakub Wojciech Klama
349*134e1779SJakub Wojciech KlamaIf there is at least a full header, though, noerror will do the obvious:
350*134e1779SJakub Wojciech Klama
351*134e1779SJakub Wojciech Klama    >>> dotl.unpack(pkt[0:1], noerror=True)
352*134e1779SJakub Wojciech Klama    Rlink(tag=None)
353*134e1779SJakub Wojciech Klama    >>> dotl.unpack(pkt[0:2], noerror=True)
354*134e1779SJakub Wojciech Klama    Rlink(tag=None)
355*134e1779SJakub Wojciech Klama
356*134e1779SJakub Wojciech KlamaIf the packet is too long, noerror suppresses the SequenceError:
357*134e1779SJakub Wojciech Klama
358*134e1779SJakub Wojciech Klama    >>> dotl.unpack(pkt + b'x')             # doctest: +IGNORE_EXCEPTION_DETAIL
359*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
360*134e1779SJakub Wojciech Klama        ...
361*134e1779SJakub Wojciech Klama    SequenceError: 1 byte(s) unconsumed
362*134e1779SJakub Wojciech Klama    >>> dotl.unpack(pkt + b'x', noerror=True)
363*134e1779SJakub Wojciech Klama    Rlink(tag=12345)
364*134e1779SJakub Wojciech Klama
365*134e1779SJakub Wojciech KlamaTo pack a stat object when producing data for reading a directory,
366*134e1779SJakub Wojciech Klamause pack_wirestat.  This puts a size in front of the packed stat
367*134e1779SJakub Wojciech Klamadata (they're represented this way in read()-of-directory data,
368*134e1779SJakub Wojciech Klamabut not elsewhere).
369*134e1779SJakub Wojciech Klama
370*134e1779SJakub Wojciech KlamaTo unpack the result of a Tstat or a read() on a directory, use
371*134e1779SJakub Wojciech Klamaunpack_wirestat.  The stat values are variable length so this
372*134e1779SJakub Wojciech Klamaworks with offsets.  If the packet is truncated, you'll get a
373*134e1779SJakub Wojciech KlamaSequenceError, but just as for header unpacking, you can use
374*134e1779SJakub Wojciech Klamanoerror to suppress this.
375*134e1779SJakub Wojciech Klama
376*134e1779SJakub Wojciech Klama(First, we'll need to build some valid packet data.)
377*134e1779SJakub Wojciech Klama
378*134e1779SJakub Wojciech Klama    >>> statobj = td.stat(type=0,dev=0,qid=td.qid(0,0,0),mode=0,
379*134e1779SJakub Wojciech Klama    ... atime=0,mtime=0,length=0,name=b'foo',uid=b'0',gid=b'0',muid=b'0')
380*134e1779SJakub Wojciech Klama    >>> data = plain.pack_wirestat(statobj)
381*134e1779SJakub Wojciech Klama    >>> len(data)
382*134e1779SJakub Wojciech Klama    55
383*134e1779SJakub Wojciech Klama
384*134e1779SJakub Wojciech KlamaNow we can unpack it:
385*134e1779SJakub Wojciech Klama
386*134e1779SJakub Wojciech Klama    >>> newobj, offset = plain.unpack_wirestat(data, 0)
387*134e1779SJakub Wojciech Klama    >>> newobj == statobj
388*134e1779SJakub Wojciech Klama    True
389*134e1779SJakub Wojciech Klama    >>> offset
390*134e1779SJakub Wojciech Klama    55
391*134e1779SJakub Wojciech Klama
392*134e1779SJakub Wojciech KlamaSince the packed data do not include the dotu extensions, we get
393*134e1779SJakub Wojciech Klamaa SequenceError if we try to unpack with dotu or dotl:
394*134e1779SJakub Wojciech Klama
395*134e1779SJakub Wojciech Klama    >>> dotu.unpack_wirestat(data, 0)       # doctest: +IGNORE_EXCEPTION_DETAIL
396*134e1779SJakub Wojciech Klama    Traceback (most recent call last):
397*134e1779SJakub Wojciech Klama        ...
398*134e1779SJakub Wojciech Klama    SequenceError: out of data while unpacking 'extension'
399*134e1779SJakub Wojciech Klama
400*134e1779SJakub Wojciech KlamaWhen using noerror, the returned new offset will be greater
401*134e1779SJakub Wojciech Klamathan the length of the packet, after a failed unpack, and some
402*134e1779SJakub Wojciech Klamaelements may be None:
403*134e1779SJakub Wojciech Klama
404*134e1779SJakub Wojciech Klama    >>> newobj, offset = plain.unpack_wirestat(data[0:10], 0, noerror=True)
405*134e1779SJakub Wojciech Klama    >>> offset
406*134e1779SJakub Wojciech Klama    55
407*134e1779SJakub Wojciech Klama    >>> newobj.length is None
408*134e1779SJakub Wojciech Klama    True
409*134e1779SJakub Wojciech Klama
410*134e1779SJakub Wojciech KlamaSimilarly, use unpack_dirent to unpack the result of a dot-L
411*134e1779SJakub Wojciech Klamareaddir(), using offsets.  (Build them with pack_dirent.)
412*134e1779SJakub Wojciech Klama
413*134e1779SJakub Wojciech Klama    >>> dirent = td.dirent(qid=td.qid(1,2,3),offset=0,
414*134e1779SJakub Wojciech Klama    ... type=td.DT_REG,name=b'foo')
415*134e1779SJakub Wojciech Klama    >>> pkt = dotl.pack_dirent(dirent)
416*134e1779SJakub Wojciech Klama    >>> len(pkt)
417*134e1779SJakub Wojciech Klama    27
418*134e1779SJakub Wojciech Klama
419*134e1779SJakub Wojciech Klamaand then:
420*134e1779SJakub Wojciech Klama
421*134e1779SJakub Wojciech Klama    >>> newde, offset = dotl.unpack_dirent(pkt, 0)
422*134e1779SJakub Wojciech Klama    >>> newde == dirent
423*134e1779SJakub Wojciech Klama    True
424*134e1779SJakub Wojciech Klama    >>> offset
425*134e1779SJakub Wojciech Klama    27
426*134e1779SJakub Wojciech Klama
427*134e1779SJakub Wojciech Klama"""
428*134e1779SJakub Wojciech Klama
429*134e1779SJakub Wojciech Klamafrom __future__ import print_function
430*134e1779SJakub Wojciech Klama
431*134e1779SJakub Wojciech Klamaimport collections
432*134e1779SJakub Wojciech Klamaimport os
433*134e1779SJakub Wojciech Klamaimport re
434*134e1779SJakub Wojciech Klamaimport sys
435*134e1779SJakub Wojciech Klama
436*134e1779SJakub Wojciech Klamaimport p9err
437*134e1779SJakub Wojciech Klamaimport pfod
438*134e1779SJakub Wojciech Klamaimport sequencer
439*134e1779SJakub Wojciech Klama
440*134e1779SJakub Wojciech KlamaSequenceError = sequencer.SequenceError
441*134e1779SJakub Wojciech Klama
442*134e1779SJakub Wojciech Klamafcall_names = {}
443*134e1779SJakub Wojciech Klama
444*134e1779SJakub Wojciech Klama# begin ???
445*134e1779SJakub Wojciech Klama# to interfere with (eg) the size part of the packet:
446*134e1779SJakub Wojciech Klama#   pkt = proto.pack(fcall=protocol.td.Tversion,
447*134e1779SJakub Wojciech Klama#       size=123, # wrong
448*134e1779SJakub Wojciech Klama#       args={ 'tag': 1, msize: 1000, version: '9p2000.u' })
449*134e1779SJakub Wojciech Klama# a standard Twrite:
450*134e1779SJakub Wojciech Klama#   pkt = proto.pack(fcall=protocol.td.Twrite,
451*134e1779SJakub Wojciech Klama#       args={ 'tag': 1, 'fid': 2, 'offset': 0, 'data': b'rawdata' })
452*134e1779SJakub Wojciech Klama# or:
453*134e1779SJakub Wojciech Klama#   pkt = proto.pack(fcall=protocol.td.Twrite,
454*134e1779SJakub Wojciech Klama#       data=proto.Twrite(tag=1, fid=2, offset=0, data=b'rawdata' })
455*134e1779SJakub Wojciech Klama# a broken Twrite:
456*134e1779SJakub Wojciech Klama#   pkt = proto.pack(fcall=protocol.td.Twrite,
457*134e1779SJakub Wojciech Klama#       args={ 'tag': 1, 'fid': 2, 'offset': 0, 'count': 99,
458*134e1779SJakub Wojciech Klama#           'data': b'rawdata' })  -- XXX won't work (yet?)
459*134e1779SJakub Wojciech Klama#
460*134e1779SJakub Wojciech Klama# build a QID: (td => typedefs and defines)
461*134e1779SJakub Wojciech Klama#    qid = protocol.td.qid(type=protocol.td.QTFILE, version=1, path=2)
462*134e1779SJakub Wojciech Klama# build the Twrite data as a data structure:
463*134e1779SJakub Wojciech Klama#    wrdata = protocol.td.Twrite(tag=1, fid=2, offset=0, data=b'rawdata')
464*134e1779SJakub Wojciech Klama#
465*134e1779SJakub Wojciech Klama# turn incoming byte stream data into a Header and remaining data:
466*134e1779SJakub Wojciech Klama#    foo = proto.pack(data)
467*134e1779SJakub Wojciech Klama
468*134e1779SJakub Wojciech Klamaclass _PackInfo(object):
469*134e1779SJakub Wojciech Klama    """
470*134e1779SJakub Wojciech Klama    Essentially just a Sequencer, except that we remember
471*134e1779SJakub Wojciech Klama    if there are any :auto annotations on any of the coders,
472*134e1779SJakub Wojciech Klama    and we check for coders that are string coders ('data[size]').
473*134e1779SJakub Wojciech Klama
474*134e1779SJakub Wojciech Klama    This could in theory be a recursive check, but in practice
475*134e1779SJakub Wojciech Klama    all the automatics are at the top level, and we have no mechanism
476*134e1779SJakub Wojciech Klama    to pass down inner automatics.
477*134e1779SJakub Wojciech Klama    """
478*134e1779SJakub Wojciech Klama    def __init__(self, seq):
479*134e1779SJakub Wojciech Klama        self.seq = seq
480*134e1779SJakub Wojciech Klama        self.autos = None
481*134e1779SJakub Wojciech Klama        for pair in seq:        # (cond, code) pair
482*134e1779SJakub Wojciech Klama            sub = pair[1]
483*134e1779SJakub Wojciech Klama            if sub.aux is None:
484*134e1779SJakub Wojciech Klama                continue
485*134e1779SJakub Wojciech Klama            assert sub.aux == 'auto' or sub.aux == 'len'
486*134e1779SJakub Wojciech Klama            if self.autos is None:
487*134e1779SJakub Wojciech Klama                self.autos = []
488*134e1779SJakub Wojciech Klama            self.autos.append(pair)
489*134e1779SJakub Wojciech Klama
490*134e1779SJakub Wojciech Klama    def __repr__(self):
491*134e1779SJakub Wojciech Klama        return '{0}({1!r})'.format(self.__class__.__name__, self.seq)
492*134e1779SJakub Wojciech Klama
493*134e1779SJakub Wojciech Klama    def pack(self, auto_vars, conditions, data, rodata):
494*134e1779SJakub Wojciech Klama        """
495*134e1779SJakub Wojciech Klama        Pack data.  Insert automatic and/or counted variables
496*134e1779SJakub Wojciech Klama        automatically, if they are not already set in the data.
497*134e1779SJakub Wojciech Klama
498*134e1779SJakub Wojciech Klama        If rodata ("read-only data") is True we make sure not
499*134e1779SJakub Wojciech Klama        to modify the caller's data.  Since data is a PFOD rather
500*134e1779SJakub Wojciech Klama        than a normal ordered dictionary, we use _copy().
501*134e1779SJakub Wojciech Klama        """
502*134e1779SJakub Wojciech Klama        if self.autos:
503*134e1779SJakub Wojciech Klama            for cond, sub in self.autos:
504*134e1779SJakub Wojciech Klama                # False conditionals don't need to be filled-in.
505*134e1779SJakub Wojciech Klama                if cond is not None and not conditions[cond]:
506*134e1779SJakub Wojciech Klama                    continue
507*134e1779SJakub Wojciech Klama                if sub.aux == 'auto':
508*134e1779SJakub Wojciech Klama                    # Automatic variable, e.g., version.  The
509*134e1779SJakub Wojciech Klama                    # sub-coder's name ('version') is the test item.
510*134e1779SJakub Wojciech Klama                    if data.get(sub.name) is None:
511*134e1779SJakub Wojciech Klama                        if rodata:
512*134e1779SJakub Wojciech Klama                            data = data._copy()
513*134e1779SJakub Wojciech Klama                            rodata = False
514*134e1779SJakub Wojciech Klama                        data[sub.name] = auto_vars[sub.name]
515*134e1779SJakub Wojciech Klama                else:
516*134e1779SJakub Wojciech Klama                    # Automatic length, e.g., data[count].  The
517*134e1779SJakub Wojciech Klama                    # sub-coders's repeat item ('count') is the
518*134e1779SJakub Wojciech Klama                    # test item.  Of course, it's possible that
519*134e1779SJakub Wojciech Klama                    # the counted item is missing as well.  If so
520*134e1779SJakub Wojciech Klama                    # we just leave both None and take the
521*134e1779SJakub Wojciech Klama                    # encoding error.
522*134e1779SJakub Wojciech Klama                    assert sub.aux == 'len'
523*134e1779SJakub Wojciech Klama                    if data.get(sub.repeat) is not None:
524*134e1779SJakub Wojciech Klama                        continue
525*134e1779SJakub Wojciech Klama                    item = data.get(sub.name)
526*134e1779SJakub Wojciech Klama                    if item is not None:
527*134e1779SJakub Wojciech Klama                        if rodata:
528*134e1779SJakub Wojciech Klama                            data = data._copy()
529*134e1779SJakub Wojciech Klama                            rodata = False
530*134e1779SJakub Wojciech Klama                        data[sub.repeat] = len(item)
531*134e1779SJakub Wojciech Klama        return self.seq.pack(data, conditions)
532*134e1779SJakub Wojciech Klama
533*134e1779SJakub Wojciech Klamaclass _P9Proto(object):
534*134e1779SJakub Wojciech Klama    def __init__(self, auto_vars, conditions, p9_data, pfods, index):
535*134e1779SJakub Wojciech Klama        self.auto_vars = auto_vars      # currently, just version
536*134e1779SJakub Wojciech Klama        self.conditions = conditions    # '.u'
537*134e1779SJakub Wojciech Klama        self.pfods = pfods # dictionary, maps pfod to packinfo
538*134e1779SJakub Wojciech Klama        self.index = index # for comparison: plain < dotu < dotl
539*134e1779SJakub Wojciech Klama
540*134e1779SJakub Wojciech Klama        self.use_rlerror = rrd.Rlerror in pfods
541*134e1779SJakub Wojciech Klama
542*134e1779SJakub Wojciech Klama        for dtype in pfods:
543*134e1779SJakub Wojciech Klama            name = dtype.__name__
544*134e1779SJakub Wojciech Klama            # For each Txxx/Rxxx, define a self.<name>() to
545*134e1779SJakub Wojciech Klama            # call self.pack_from().
546*134e1779SJakub Wojciech Klama            #
547*134e1779SJakub Wojciech Klama            # The packinfo is from _Packinfo(seq); the fcall and
548*134e1779SJakub Wojciech Klama            # seq come from p9_data.protocol[<name>].
549*134e1779SJakub Wojciech Klama            proto_tuple = p9_data.protocol[name]
550*134e1779SJakub Wojciech Klama            assert dtype == proto_tuple[0]
551*134e1779SJakub Wojciech Klama            packinfo = pfods[dtype]
552*134e1779SJakub Wojciech Klama            # in theory we can do this with no names using nested
553*134e1779SJakub Wojciech Klama            # lambdas, but that's just too confusing, so let's
554*134e1779SJakub Wojciech Klama            # do it with nested functions instead.
555*134e1779SJakub Wojciech Klama            def builder(constructor=dtype, packinfo=packinfo):
556*134e1779SJakub Wojciech Klama                "return function that calls _pack_from with built PFOD"
557*134e1779SJakub Wojciech Klama                def invoker(self, *args, **kwargs):
558*134e1779SJakub Wojciech Klama                    "build PFOD and call _pack_from"
559*134e1779SJakub Wojciech Klama                    return self._pack_from(constructor(*args, **kwargs),
560*134e1779SJakub Wojciech Klama                                           rodata=False, caller=None,
561*134e1779SJakub Wojciech Klama                                           packinfo=packinfo)
562*134e1779SJakub Wojciech Klama                return invoker
563*134e1779SJakub Wojciech Klama            func = builder()
564*134e1779SJakub Wojciech Klama            func.__name__ = name
565*134e1779SJakub Wojciech Klama            func.__doc__ = 'pack from {0}'.format(name)
566*134e1779SJakub Wojciech Klama            setattr(self.__class__, name, func)
567*134e1779SJakub Wojciech Klama
568*134e1779SJakub Wojciech Klama    def __repr__(self):
569*134e1779SJakub Wojciech Klama        return '{0}({1!r})'.format(self.__class__.__name__, self.version)
570*134e1779SJakub Wojciech Klama
571*134e1779SJakub Wojciech Klama    def __str__(self):
572*134e1779SJakub Wojciech Klama        return self.version
573*134e1779SJakub Wojciech Klama
574*134e1779SJakub Wojciech Klama    # define rich-comparison operators, so we can, e.g., test vers > plain
575*134e1779SJakub Wojciech Klama    def __lt__(self, other):
576*134e1779SJakub Wojciech Klama        return self.index < other.index
577*134e1779SJakub Wojciech Klama    def __le__(self, other):
578*134e1779SJakub Wojciech Klama        return self.index <= other.index
579*134e1779SJakub Wojciech Klama    def __eq__(self, other):
580*134e1779SJakub Wojciech Klama        return self.index == other.index
581*134e1779SJakub Wojciech Klama    def __ne__(self, other):
582*134e1779SJakub Wojciech Klama        return self.index != other.index
583*134e1779SJakub Wojciech Klama    def __gt__(self, other):
584*134e1779SJakub Wojciech Klama        return self.index > other.index
585*134e1779SJakub Wojciech Klama    def __ge__(self, other):
586*134e1779SJakub Wojciech Klama        return self.index >= other.index
587*134e1779SJakub Wojciech Klama
588*134e1779SJakub Wojciech Klama    def downgrade_to(self, other_name):
589*134e1779SJakub Wojciech Klama        """
590*134e1779SJakub Wojciech Klama        Downgrade from this protocol to a not-greater one.
591*134e1779SJakub Wojciech Klama
592*134e1779SJakub Wojciech Klama        Raises KeyError if other_name is not a valid protocol,
593*134e1779SJakub Wojciech Klama        or this is not a downgrade (with setting back to self
594*134e1779SJakub Wojciech Klama        considered a valid "downgrade", i.e., we're doing subseteq
595*134e1779SJakub Wojciech Klama        rather than subset).
596*134e1779SJakub Wojciech Klama        """
597*134e1779SJakub Wojciech Klama        if not isinstance(other_name, str) and isinstance(other_name, bytes):
598*134e1779SJakub Wojciech Klama            other_name = other_name.decode('utf-8', 'surrogateescape')
599*134e1779SJakub Wojciech Klama        other = p9_version(other_name)
600*134e1779SJakub Wojciech Klama        if other > self:
601*134e1779SJakub Wojciech Klama            raise KeyError(other_name)
602*134e1779SJakub Wojciech Klama        return other
603*134e1779SJakub Wojciech Klama
604*134e1779SJakub Wojciech Klama    def error(self, tag, err):
605*134e1779SJakub Wojciech Klama        "produce Rerror or Rlerror, whichever is appropriate"
606*134e1779SJakub Wojciech Klama        if isinstance(err, Exception):
607*134e1779SJakub Wojciech Klama            errnum = err.errno
608*134e1779SJakub Wojciech Klama            errmsg = err.strerror
609*134e1779SJakub Wojciech Klama        else:
610*134e1779SJakub Wojciech Klama            errnum = err
611*134e1779SJakub Wojciech Klama            errmsg = os.strerror(errnum)
612*134e1779SJakub Wojciech Klama        if self.use_rlerror:
613*134e1779SJakub Wojciech Klama            return self.Rlerror(tag=tag, ecode=p9err.to_dotl(errnum))
614*134e1779SJakub Wojciech Klama        return self.Rerror(tag=tag, errstr=errmsg,
615*134e1779SJakub Wojciech Klama                           errnum=p9err.to_dotu(errnum))
616*134e1779SJakub Wojciech Klama
617*134e1779SJakub Wojciech Klama    def pack(self, *args, **kwargs):
618*134e1779SJakub Wojciech Klama        "pack up a pfod or fcall-and-arguments"
619*134e1779SJakub Wojciech Klama        fcall = kwargs.pop('fcall', None)
620*134e1779SJakub Wojciech Klama        if fcall is None:
621*134e1779SJakub Wojciech Klama            # Called without fcall=...
622*134e1779SJakub Wojciech Klama            # This requires that args have one argument that
623*134e1779SJakub Wojciech Klama            # is the PFOD; kwargs should be empty (but we'll take
624*134e1779SJakub Wojciech Klama            # data=pfod as well).  The size is implied, and
625*134e1779SJakub Wojciech Klama            # fcall comes from the pfod.
626*134e1779SJakub Wojciech Klama            data = kwargs.pop('data', None)
627*134e1779SJakub Wojciech Klama            if data is None:
628*134e1779SJakub Wojciech Klama                if len(args) != 1:
629*134e1779SJakub Wojciech Klama                    raise TypeError('pack() with no fcall requires 1 argument')
630*134e1779SJakub Wojciech Klama                data = args[0]
631*134e1779SJakub Wojciech Klama            if len(kwargs):
632*134e1779SJakub Wojciech Klama                raise TypeError('pack() got an unexpected keyword argument '
633*134e1779SJakub Wojciech Klama                                '{0}'.format(kwargs.popitem()[0]))
634*134e1779SJakub Wojciech Klama            return self._pack_from(data, True, 'pack', None)
635*134e1779SJakub Wojciech Klama
636*134e1779SJakub Wojciech Klama        # Called as pack(fcall=whatever, data={...}).
637*134e1779SJakub Wojciech Klama        # The data argument must be a dictionary since we're going to
638*134e1779SJakub Wojciech Klama        # apply ** to it in the call to build the PFOD.  Note that
639*134e1779SJakub Wojciech Klama        # it could already be a PFOD, which is OK, but we're going to
640*134e1779SJakub Wojciech Klama        # copy it to a new one regardless (callers that have a PFOD
641*134e1779SJakub Wojciech Klama        # should use pack_from instead).
642*134e1779SJakub Wojciech Klama        if len(args):
643*134e1779SJakub Wojciech Klama            raise TypeError('pack() got unexpected arguments '
644*134e1779SJakub Wojciech Klama                            '{0!r}'.format(args))
645*134e1779SJakub Wojciech Klama        data = kwargs.pop('args', None)
646*134e1779SJakub Wojciech Klama        if len(kwargs):
647*134e1779SJakub Wojciech Klama            raise TypeError('pack() got an unexpected keyword argument '
648*134e1779SJakub Wojciech Klama                            '{0}'.format(kwargs.popitem()[0]))
649*134e1779SJakub Wojciech Klama        if not isinstance(data, dict):
650*134e1779SJakub Wojciech Klama            raise TypeError('pack() with fcall and data '
651*134e1779SJakub Wojciech Klama                            'requires data to be a dictionary')
652*134e1779SJakub Wojciech Klama        try:
653*134e1779SJakub Wojciech Klama            name = fcall_names[fcall]
654*134e1779SJakub Wojciech Klama        except KeyError:
655*134e1779SJakub Wojciech Klama            raise TypeError('pack(): {0} is not a valid '
656*134e1779SJakub Wojciech Klama                            'fcall value'.format(fcall))
657*134e1779SJakub Wojciech Klama        cls = getattr(rrd, name)
658*134e1779SJakub Wojciech Klama        data = cls(**data)
659*134e1779SJakub Wojciech Klama        return self._pack_from(data, False, 'pack', None)
660*134e1779SJakub Wojciech Klama
661*134e1779SJakub Wojciech Klama    def pack_from(self, data):
662*134e1779SJakub Wojciech Klama        "pack from pfod data, using its type to determine fcall"
663*134e1779SJakub Wojciech Klama        return self._pack_from(data, True, 'pack_from', None)
664*134e1779SJakub Wojciech Klama
665*134e1779SJakub Wojciech Klama    def _pack_from(self, data, rodata, caller, packinfo):
666*134e1779SJakub Wojciech Klama        """
667*134e1779SJakub Wojciech Klama        Internal pack(): called from both invokers (self.Tversion,
668*134e1779SJakub Wojciech Klama        self.Rwalk, etc.) and from pack and pack_from methods.
669*134e1779SJakub Wojciech Klama        "caller" says which.  If rodata is True we're not supposed to
670*134e1779SJakub Wojciech Klama        modify the incoming data, as it may belong to someone
671*134e1779SJakub Wojciech Klama        else.  Some calls to pack() build a PFOD and hence pass in
672*134e1779SJakub Wojciech Klama        False.
673*134e1779SJakub Wojciech Klama
674*134e1779SJakub Wojciech Klama        The predefined invokers pass in a preconstructed PFOD,
675*134e1779SJakub Wojciech Klama        *and* set rodata=False, *and* provide a packinfo, so that
676*134e1779SJakub Wojciech Klama        we never have to copy, nor look up the packinfo.
677*134e1779SJakub Wojciech Klama        """
678*134e1779SJakub Wojciech Klama        if caller is not None:
679*134e1779SJakub Wojciech Klama            assert caller in ('pack', 'pack_from') and packinfo is None
680*134e1779SJakub Wojciech Klama            # Indirect call from pack_from(), or from pack() after
681*134e1779SJakub Wojciech Klama            # pack() built a PFOD.  We make sure this kind of PFOD
682*134e1779SJakub Wojciech Klama            # is allowed for this protocol.
683*134e1779SJakub Wojciech Klama            packinfo = self.pfods.get(data.__class__, None)
684*134e1779SJakub Wojciech Klama            if packinfo is None:
685*134e1779SJakub Wojciech Klama                raise TypeError('{0}({1!r}): invalid '
686*134e1779SJakub Wojciech Klama                                'input'.format(caller, data))
687*134e1779SJakub Wojciech Klama
688*134e1779SJakub Wojciech Klama        # Pack the data
689*134e1779SJakub Wojciech Klama        pkt = packinfo.pack(self.auto_vars, self.conditions, data, rodata)
690*134e1779SJakub Wojciech Klama
691*134e1779SJakub Wojciech Klama        fcall = data.__class__.__name__
692*134e1779SJakub Wojciech Klama        fcall_code = getattr(td, fcall)
693*134e1779SJakub Wojciech Klama
694*134e1779SJakub Wojciech Klama        # That's the inner data; now we must add the header,
695*134e1779SJakub Wojciech Klama        # with fcall (translated back to byte code value) and
696*134e1779SJakub Wojciech Klama        # outer data.  The size is implied by len(pkt).  There
697*134e1779SJakub Wojciech Klama        # are no other auto variables, and no conditions.
698*134e1779SJakub Wojciech Klama        #
699*134e1779SJakub Wojciech Klama        # NB: the size includes the size of the header itself
700*134e1779SJakub Wojciech Klama        # and the fcall code byte, plus the size of the data.
701*134e1779SJakub Wojciech Klama        data = _9p_data.header_pfod(size=4 + 1 + len(pkt), dsize=len(pkt),
702*134e1779SJakub Wojciech Klama                                    fcall=fcall_code, data=pkt)
703*134e1779SJakub Wojciech Klama        empty = None # logically should be {}, but not actually used below
704*134e1779SJakub Wojciech Klama        pkt = _9p_data.header_pack_seq.pack(data, empty)
705*134e1779SJakub Wojciech Klama        return pkt
706*134e1779SJakub Wojciech Klama
707*134e1779SJakub Wojciech Klama    @staticmethod
708*134e1779SJakub Wojciech Klama    def unpack_header(bstring, noerror=False):
709*134e1779SJakub Wojciech Klama        """
710*134e1779SJakub Wojciech Klama        Unpack header.
711*134e1779SJakub Wojciech Klama
712*134e1779SJakub Wojciech Klama        We know that our caller has already stripped off the
713*134e1779SJakub Wojciech Klama        overall size field (4 bytes), leaving us with the fcall
714*134e1779SJakub Wojciech Klama        (1 byte) and data (len(bstring)-1 bytes).  If len(bstring)
715*134e1779SJakub Wojciech Klama        is 0, this is an invalid header: set dsize to 0 and let
716*134e1779SJakub Wojciech Klama        fcall become None, if noerror is set.
717*134e1779SJakub Wojciech Klama        """
718*134e1779SJakub Wojciech Klama        vdict = _9p_data.header_pfod()
719*134e1779SJakub Wojciech Klama        vdict['size'] = len(bstring) + 4
720*134e1779SJakub Wojciech Klama        vdict['dsize'] = max(0, len(bstring) - 1)
721*134e1779SJakub Wojciech Klama        _9p_data.header_unpack_seq.unpack(vdict, None, bstring, noerror)
722*134e1779SJakub Wojciech Klama        return vdict
723*134e1779SJakub Wojciech Klama
724*134e1779SJakub Wojciech Klama    def unpack(self, bstring, noerror=False):
725*134e1779SJakub Wojciech Klama        "produce filled PFOD from fcall in packet"
726*134e1779SJakub Wojciech Klama        vdict = self.unpack_header(bstring, noerror)
727*134e1779SJakub Wojciech Klama        # NB: vdict['dsize'] is used internally during unpack, to
728*134e1779SJakub Wojciech Klama        # find out how many bytes to copy to vdict['data'], but by
729*134e1779SJakub Wojciech Klama        # the time unpack is done, we no longer need it.
730*134e1779SJakub Wojciech Klama        #
731*134e1779SJakub Wojciech Klama        # size = vdict['size']
732*134e1779SJakub Wojciech Klama        # dsize = vdict['dsize']
733*134e1779SJakub Wojciech Klama        fcall = vdict['fcall']
734*134e1779SJakub Wojciech Klama        data = vdict['data']
735*134e1779SJakub Wojciech Klama        # Note: it's possible for size and/or fcall to be None,
736*134e1779SJakub Wojciech Klama        # when noerror is true.  However, if we support fcall, then
737*134e1779SJakub Wojciech Klama        # clearly fcall is not None; and since fcall follows size,
738*134e1779SJakub Wojciech Klama        # we can always proceed if we support fcall.
739*134e1779SJakub Wojciech Klama        if self.supports(fcall):
740*134e1779SJakub Wojciech Klama            fcall = fcall_names[fcall]
741*134e1779SJakub Wojciech Klama            cls = getattr(rrd, fcall)
742*134e1779SJakub Wojciech Klama            seq = self.pfods[cls].seq
743*134e1779SJakub Wojciech Klama        elif fcall == td.Rlerror:
744*134e1779SJakub Wojciech Klama            # As a special case for diod, we accept Rlerror even
745*134e1779SJakub Wojciech Klama            # if it's not formally part of the protocol.
746*134e1779SJakub Wojciech Klama            cls = rrd.Rlerror
747*134e1779SJakub Wojciech Klama            seq = dotl.pfods[rrd.Rlerror].seq
748*134e1779SJakub Wojciech Klama        else:
749*134e1779SJakub Wojciech Klama            fcall = fcall_names.get(fcall, fcall)
750*134e1779SJakub Wojciech Klama            raise SequenceError('invalid fcall {0!r} for '
751*134e1779SJakub Wojciech Klama                                '{1}'.format(fcall, self))
752*134e1779SJakub Wojciech Klama        vdict = cls()
753*134e1779SJakub Wojciech Klama        seq.unpack(vdict, self.conditions, data, noerror)
754*134e1779SJakub Wojciech Klama        return vdict
755*134e1779SJakub Wojciech Klama
756*134e1779SJakub Wojciech Klama    def pack_wirestat(self, statobj):
757*134e1779SJakub Wojciech Klama        """
758*134e1779SJakub Wojciech Klama        Pack a stat object to appear as data returned by read()
759*134e1779SJakub Wojciech Klama        on a directory.  Essentially, we prefix the data with a size.
760*134e1779SJakub Wojciech Klama        """
761*134e1779SJakub Wojciech Klama        data = td.stat_seq.pack(statobj, self.conditions)
762*134e1779SJakub Wojciech Klama        return td.wirestat_seq.pack({'size': len(data), 'data': data}, {})
763*134e1779SJakub Wojciech Klama
764*134e1779SJakub Wojciech Klama    def unpack_wirestat(self, bstring, offset, noerror=False):
765*134e1779SJakub Wojciech Klama        """
766*134e1779SJakub Wojciech Klama        Produce the next td.stat object from byte-string,
767*134e1779SJakub Wojciech Klama        returning it and new offset.
768*134e1779SJakub Wojciech Klama        """
769*134e1779SJakub Wojciech Klama        statobj = td.stat()
770*134e1779SJakub Wojciech Klama        d = { 'size': None }
771*134e1779SJakub Wojciech Klama        newoff = td.wirestat_seq.unpack_from(d, self.conditions, bstring,
772*134e1779SJakub Wojciech Klama                                             offset, noerror)
773*134e1779SJakub Wojciech Klama        size = d['size']
774*134e1779SJakub Wojciech Klama        if size is None:        # implies noerror; newoff==offset+2
775*134e1779SJakub Wojciech Klama            return statobj, newoff
776*134e1779SJakub Wojciech Klama        # We now have size and data.  If noerror, data might be
777*134e1779SJakub Wojciech Klama        # too short, in which case we'll unpack a partial statobj.
778*134e1779SJakub Wojciech Klama        # Or (with or without noeror), data might be too long, so
779*134e1779SJakub Wojciech Klama        # that while len(data) == size, not all the data get used.
780*134e1779SJakub Wojciech Klama        # That may be allowed by the protocol: it's not clear.
781*134e1779SJakub Wojciech Klama        data = d['data']
782*134e1779SJakub Wojciech Klama        used = td.stat_seq.unpack_from(statobj, self.conditions, data,
783*134e1779SJakub Wojciech Klama                                       0, noerror)
784*134e1779SJakub Wojciech Klama        # if size != used ... then what?
785*134e1779SJakub Wojciech Klama        return statobj, newoff
786*134e1779SJakub Wojciech Klama
787*134e1779SJakub Wojciech Klama    def pack_dirent(self, dirent):
788*134e1779SJakub Wojciech Klama        """
789*134e1779SJakub Wojciech Klama        Dirents (dot-L only) are easy to pack, but we provide
790*134e1779SJakub Wojciech Klama        this function for symmetry.  (Should we raise an error
791*134e1779SJakub Wojciech Klama        if called on plain or dotu?)
792*134e1779SJakub Wojciech Klama        """
793*134e1779SJakub Wojciech Klama        return td.dirent_seq.pack(dirent, self.conditions)
794*134e1779SJakub Wojciech Klama
795*134e1779SJakub Wojciech Klama    def unpack_dirent(self, bstring, offset, noerror=False):
796*134e1779SJakub Wojciech Klama        """
797*134e1779SJakub Wojciech Klama        Produces the next td.dirent object from byte-string,
798*134e1779SJakub Wojciech Klama        returning it and new offset.
799*134e1779SJakub Wojciech Klama        """
800*134e1779SJakub Wojciech Klama        deobj = td.dirent()
801*134e1779SJakub Wojciech Klama        offset = td.dirent_seq.unpack_from(deobj, self.conditions, bstring,
802*134e1779SJakub Wojciech Klama                                           offset, noerror)
803*134e1779SJakub Wojciech Klama        return deobj, offset
804*134e1779SJakub Wojciech Klama
805*134e1779SJakub Wojciech Klama    def supports(self, fcall):
806*134e1779SJakub Wojciech Klama        """
807*134e1779SJakub Wojciech Klama        Return True if and only if this protocol supports the
808*134e1779SJakub Wojciech Klama        given fcall.
809*134e1779SJakub Wojciech Klama
810*134e1779SJakub Wojciech Klama        >>> plain.supports(100)
811*134e1779SJakub Wojciech Klama        True
812*134e1779SJakub Wojciech Klama        >>> plain.supports('Tversion')
813*134e1779SJakub Wojciech Klama        True
814*134e1779SJakub Wojciech Klama        >>> plain.supports('Rlink')
815*134e1779SJakub Wojciech Klama        False
816*134e1779SJakub Wojciech Klama        """
817*134e1779SJakub Wojciech Klama        fcall = fcall_names.get(fcall, None)
818*134e1779SJakub Wojciech Klama        if fcall is None:
819*134e1779SJakub Wojciech Klama            return False
820*134e1779SJakub Wojciech Klama        cls = getattr(rrd, fcall)
821*134e1779SJakub Wojciech Klama        return cls in self.pfods
822*134e1779SJakub Wojciech Klama
823*134e1779SJakub Wojciech Klama    def get_version(self, as_bytes=True):
824*134e1779SJakub Wojciech Klama        "get Plan 9 protocol version, as string or (default) as bytes"
825*134e1779SJakub Wojciech Klama        ret = self.auto_vars['version']
826*134e1779SJakub Wojciech Klama        if as_bytes and not isinstance(ret, bytes):
827*134e1779SJakub Wojciech Klama            ret = ret.encode('utf-8')
828*134e1779SJakub Wojciech Klama        return ret
829*134e1779SJakub Wojciech Klama
830*134e1779SJakub Wojciech Klama    @property
831*134e1779SJakub Wojciech Klama    def version(self):
832*134e1779SJakub Wojciech Klama        "Plan 9 protocol version"
833*134e1779SJakub Wojciech Klama        return self.get_version(as_bytes=False)
834*134e1779SJakub Wojciech Klama
835*134e1779SJakub Wojciech KlamaDEBUG = False
836*134e1779SJakub Wojciech Klama
837*134e1779SJakub Wojciech Klama# This defines a special en/decoder named "s" using a magic
838*134e1779SJakub Wojciech Klama# builtin.  This and stat are the only variable-length
839*134e1779SJakub Wojciech Klama# decoders, and this is the only recursively-variable-length
840*134e1779SJakub Wojciech Klama# one (i.e., stat decoding is effectively fixed size once we
841*134e1779SJakub Wojciech Klama# handle strings).  So this magic avoids the need for recursion.
842*134e1779SJakub Wojciech Klama#
843*134e1779SJakub Wojciech Klama# Note that _string_ is, in effect, size[2] orig_var[size].
844*134e1779SJakub Wojciech Klama_STRING_MAGIC = '_string_'
845*134e1779SJakub Wojciech KlamaSDesc = "typedef s: " + _STRING_MAGIC
846*134e1779SJakub Wojciech Klama
847*134e1779SJakub Wojciech Klama# This defines an en/decoder for type "qid",
848*134e1779SJakub Wojciech Klama# which en/decodes 1 byte called type, 4 called version, and
849*134e1779SJakub Wojciech Klama# 8 called path (for a total of 13 bytes).
850*134e1779SJakub Wojciech Klama#
851*134e1779SJakub Wojciech Klama# It also defines QTDIR, QTAPPEND, etc.  (These are not used
852*134e1779SJakub Wojciech Klama# for en/decode, or at least not yet.)
853*134e1779SJakub Wojciech KlamaQIDDesc = """\
854*134e1779SJakub Wojciech Klamatypedef qid: type[1] version[4] path[8]
855*134e1779SJakub Wojciech Klama
856*134e1779SJakub Wojciech Klama    #define QTDIR       0x80
857*134e1779SJakub Wojciech Klama    #define QTAPPEND    0x40
858*134e1779SJakub Wojciech Klama    #define QTEXCL      0x20
859*134e1779SJakub Wojciech Klama    #define QTMOUNT     0x10
860*134e1779SJakub Wojciech Klama    #define QTAUTH      0x08
861*134e1779SJakub Wojciech Klama    #define QTTMP       0x04
862*134e1779SJakub Wojciech Klama    #define QTSYMLINK   0x02
863*134e1779SJakub Wojciech Klama    #define QTFILE      0x00
864*134e1779SJakub Wojciech Klama"""
865*134e1779SJakub Wojciech Klama
866*134e1779SJakub Wojciech Klama# This defines a stat decoder, which has a 9p2000 standard front,
867*134e1779SJakub Wojciech Klama# followed by an optional additional portion.
868*134e1779SJakub Wojciech Klama#
869*134e1779SJakub Wojciech Klama# The constants are named DMDIR etc.
870*134e1779SJakub Wojciech KlamaSTATDesc = """
871*134e1779SJakub Wojciech Klamatypedef stat: type[2] dev[4] qid[qid] mode[4] atime[4] mtime[4] \
872*134e1779SJakub Wojciech Klamalength[8] name[s] uid[s] gid[s] muid[s] \
873*134e1779SJakub Wojciech Klama{.u: extension[s] n_uid[4] n_gid[4] n_muid[4] }
874*134e1779SJakub Wojciech Klama
875*134e1779SJakub Wojciech Klama    #define DMDIR           0x80000000
876*134e1779SJakub Wojciech Klama    #define DMAPPEND        0x40000000
877*134e1779SJakub Wojciech Klama    #define DMMOUNT         0x10000000
878*134e1779SJakub Wojciech Klama    #define DMAUTH          0x08000000
879*134e1779SJakub Wojciech Klama    #define DMTMP           0x04000000
880*134e1779SJakub Wojciech Klama    #define DMSYMLINK       0x02000000
881*134e1779SJakub Wojciech Klama            /* 9P2000.u extensions */
882*134e1779SJakub Wojciech Klama    #define DMDEVICE        0x00800000
883*134e1779SJakub Wojciech Klama    #define DMNAMEDPIPE     0x00200000
884*134e1779SJakub Wojciech Klama    #define DMSOCKET        0x00100000
885*134e1779SJakub Wojciech Klama    #define DMSETUID        0x00080000
886*134e1779SJakub Wojciech Klama    #define DMSETGID        0x00040000
887*134e1779SJakub Wojciech Klama"""
888*134e1779SJakub Wojciech Klama
889*134e1779SJakub Wojciech Klama# This defines a wirestat decoder.  A wirestat is a size and then
890*134e1779SJakub Wojciech Klama# a (previously encoded, or future-decoded) stat.
891*134e1779SJakub Wojciech KlamaWirestatDesc = """
892*134e1779SJakub Wojciech Klamatypedef wirestat: size[2] data[size]
893*134e1779SJakub Wojciech Klama"""
894*134e1779SJakub Wojciech Klama
895*134e1779SJakub Wojciech Klama# This defines a dirent decoder, which has a dot-L specific format.
896*134e1779SJakub Wojciech Klama#
897*134e1779SJakub Wojciech Klama# The dirent type fields are defined as DT_* (same as BSD and Linux).
898*134e1779SJakub Wojciech KlamaDirentDesc = """
899*134e1779SJakub Wojciech Klamatypedef dirent: qid[qid] offset[8] type[1] name[s]
900*134e1779SJakub Wojciech Klama
901*134e1779SJakub Wojciech Klama    #define DT_UNKNOWN       0
902*134e1779SJakub Wojciech Klama    #define DT_FIFO          1
903*134e1779SJakub Wojciech Klama    #define DT_CHR           2
904*134e1779SJakub Wojciech Klama    #define DT_DIR           4
905*134e1779SJakub Wojciech Klama    #define DT_BLK           6
906*134e1779SJakub Wojciech Klama    #define DT_REG           8
907*134e1779SJakub Wojciech Klama    #define DT_LNK          10
908*134e1779SJakub Wojciech Klama    #define DT_SOCK         12
909*134e1779SJakub Wojciech Klama    #define DT_WHT          14
910*134e1779SJakub Wojciech Klama"""
911*134e1779SJakub Wojciech Klama
912*134e1779SJakub Wojciech Klama# N.B.: this is largely a slightly more rigidly formatted variant of
913*134e1779SJakub Wojciech Klama# the contents of:
914*134e1779SJakub Wojciech Klama# https://github.com/chaos/diod/blob/master/protocol.md
915*134e1779SJakub Wojciech Klama#
916*134e1779SJakub Wojciech Klama# Note that <name> = <value>: ... assigns names for the fcall
917*134e1779SJakub Wojciech Klama# fcall (function call) table.  Names without "= value" are
918*134e1779SJakub Wojciech Klama# assumed to be the previous value +1 (and the two names are
919*134e1779SJakub Wojciech Klama# also checked to make sure they are Tfoo,Rfoo).
920*134e1779SJakub Wojciech KlamaProtocolDesc = """\
921*134e1779SJakub Wojciech KlamaRlerror.L = 7: tag[2] ecode[4]
922*134e1779SJakub Wojciech Klama    ecode is a numerical Linux errno
923*134e1779SJakub Wojciech Klama
924*134e1779SJakub Wojciech KlamaTstatfs.L = 8: tag[2] fid[4]
925*134e1779SJakub Wojciech KlamaRstatfs.L: tag[2] type[4] bsize[4] blocks[8] bfree[8] bavail[8] \
926*134e1779SJakub Wojciech Klama         files[8] ffree[8] fsid[8] namelen[4]
927*134e1779SJakub Wojciech Klama    Rstatfs corresponds to Linux statfs structure:
928*134e1779SJakub Wojciech Klama    struct statfs {
929*134e1779SJakub Wojciech Klama        long    f_type;     /* type of file system */
930*134e1779SJakub Wojciech Klama        long    f_bsize;    /* optimal transfer block size */
931*134e1779SJakub Wojciech Klama        long    f_blocks;   /* total data blocks in file system */
932*134e1779SJakub Wojciech Klama        long    f_bfree;    /* free blocks in fs */
933*134e1779SJakub Wojciech Klama        long    f_bavail;   /* free blocks avail to non-superuser */
934*134e1779SJakub Wojciech Klama        long    f_files;    /* total file nodes in file system */
935*134e1779SJakub Wojciech Klama        long    f_ffree;    /* free file nodes in fs */
936*134e1779SJakub Wojciech Klama        fsid_t  f_fsid;     /* file system id */
937*134e1779SJakub Wojciech Klama        long    f_namelen;  /* maximum length of filenames */
938*134e1779SJakub Wojciech Klama    };
939*134e1779SJakub Wojciech Klama
940*134e1779SJakub Wojciech Klama    This comes from nowhere obvious...
941*134e1779SJakub Wojciech Klama        #define FSTYPE      0x01021997
942*134e1779SJakub Wojciech Klama
943*134e1779SJakub Wojciech KlamaTlopen.L = 12: tag[2] fid[4] flags[4]
944*134e1779SJakub Wojciech KlamaRlopen.L: tag[2] qid[qid] iounit[4]
945*134e1779SJakub Wojciech Klama    lopen prepares fid for file (or directory) I/O.
946*134e1779SJakub Wojciech Klama
947*134e1779SJakub Wojciech Klama    flags contains Linux open(2) flag bits, e.g., O_RDONLY, O_RDWR, O_WRONLY.
948*134e1779SJakub Wojciech Klama
949*134e1779SJakub Wojciech Klama        #define L_O_CREAT       000000100
950*134e1779SJakub Wojciech Klama        #define L_O_EXCL        000000200
951*134e1779SJakub Wojciech Klama        #define L_O_NOCTTY      000000400
952*134e1779SJakub Wojciech Klama        #define L_O_TRUNC       000001000
953*134e1779SJakub Wojciech Klama        #define L_O_APPEND      000002000
954*134e1779SJakub Wojciech Klama        #define L_O_NONBLOCK    000004000
955*134e1779SJakub Wojciech Klama        #define L_O_DSYNC       000010000
956*134e1779SJakub Wojciech Klama        #define L_O_FASYNC      000020000
957*134e1779SJakub Wojciech Klama        #define L_O_DIRECT      000040000
958*134e1779SJakub Wojciech Klama        #define L_O_LARGEFILE   000100000
959*134e1779SJakub Wojciech Klama        #define L_O_DIRECTORY   000200000
960*134e1779SJakub Wojciech Klama        #define L_O_NOFOLLOW    000400000
961*134e1779SJakub Wojciech Klama        #define L_O_NOATIME     001000000
962*134e1779SJakub Wojciech Klama        #define L_O_CLOEXEC     002000000
963*134e1779SJakub Wojciech Klama        #define L_O_SYNC        004000000
964*134e1779SJakub Wojciech Klama        #define L_O_PATH        010000000
965*134e1779SJakub Wojciech Klama        #define L_O_TMPFILE     020000000
966*134e1779SJakub Wojciech Klama
967*134e1779SJakub Wojciech KlamaTlcreate.L = 14: tag[2] fid[4] name[s] flags[4] mode[4] gid[4]
968*134e1779SJakub Wojciech KlamaRlcreate.L: tag[2] qid[qid] iounit[4]
969*134e1779SJakub Wojciech Klama    lcreate creates a regular file name in directory fid and prepares
970*134e1779SJakub Wojciech Klama    it for I/O.
971*134e1779SJakub Wojciech Klama
972*134e1779SJakub Wojciech Klama    fid initially represents the parent directory of the new file.
973*134e1779SJakub Wojciech Klama    After the call it represents the new file.
974*134e1779SJakub Wojciech Klama
975*134e1779SJakub Wojciech Klama    flags contains Linux open(2) flag bits (including O_CREAT).
976*134e1779SJakub Wojciech Klama
977*134e1779SJakub Wojciech Klama    mode contains Linux creat(2) mode (permissions) bits.
978*134e1779SJakub Wojciech Klama
979*134e1779SJakub Wojciech Klama    gid is the effective gid of the caller.
980*134e1779SJakub Wojciech Klama
981*134e1779SJakub Wojciech KlamaTsymlink.L = 16: tag[2] dfid[4] name[s] symtgt[s] gid[4]
982*134e1779SJakub Wojciech KlamaRsymlink.L: tag[2] qid[qid]
983*134e1779SJakub Wojciech Klama    symlink creates a symbolic link name in directory dfid.  The
984*134e1779SJakub Wojciech Klama    link will point to symtgt.
985*134e1779SJakub Wojciech Klama
986*134e1779SJakub Wojciech Klama    gid is the effective group id of the caller.
987*134e1779SJakub Wojciech Klama
988*134e1779SJakub Wojciech Klama    The qid for the new symbolic link is returned in the reply.
989*134e1779SJakub Wojciech Klama
990*134e1779SJakub Wojciech KlamaTmknod.L = 18: tag[2] dfid[4] name[s] mode[4] major[4] minor[4] gid[4]
991*134e1779SJakub Wojciech KlamaRmknod.L: tag[2] qid[qid]
992*134e1779SJakub Wojciech Klama    mknod creates a device node name in directory dfid with major
993*134e1779SJakub Wojciech Klama    and minor numbers.
994*134e1779SJakub Wojciech Klama
995*134e1779SJakub Wojciech Klama    mode contains Linux mknod(2) mode bits.  (Note that these
996*134e1779SJakub Wojciech Klama    include the S_IFMT bits which may be S_IFBLK, S_IFCHR, or
997*134e1779SJakub Wojciech Klama    S_IFSOCK.)
998*134e1779SJakub Wojciech Klama
999*134e1779SJakub Wojciech Klama    gid is the effective group id of the caller.
1000*134e1779SJakub Wojciech Klama
1001*134e1779SJakub Wojciech Klama    The qid for the new device node is returned in the reply.
1002*134e1779SJakub Wojciech Klama
1003*134e1779SJakub Wojciech KlamaTrename.L = 20: tag[2] fid[4] dfid[4] name[s]
1004*134e1779SJakub Wojciech KlamaRrename.L: tag[2]
1005*134e1779SJakub Wojciech Klama    rename renames a file system object referenced by fid, to name
1006*134e1779SJakub Wojciech Klama    in the directory referenced by dfid.
1007*134e1779SJakub Wojciech Klama
1008*134e1779SJakub Wojciech Klama    This operation will eventually be replaced by renameat.
1009*134e1779SJakub Wojciech Klama
1010*134e1779SJakub Wojciech KlamaTreadlink.L = 22: tag[2] fid[4]
1011*134e1779SJakub Wojciech KlamaRreadlink.L: tag[2] target[s]
1012*134e1779SJakub Wojciech Klama    readlink returns the contents of teh symbolic link referenced by fid.
1013*134e1779SJakub Wojciech Klama
1014*134e1779SJakub Wojciech KlamaTgetattr.L = 24: tag[2] fid[4] request_mask[8]
1015*134e1779SJakub Wojciech KlamaRgetattr.L: tag[2] valid[8] qid[qid] mode[4] uid[4] gid[4] nlink[8] \
1016*134e1779SJakub Wojciech Klama          rdev[8] size[8] blksize[8] blocks[8] \
1017*134e1779SJakub Wojciech Klama          atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8] \
1018*134e1779SJakub Wojciech Klama          ctime_sec[8] ctime_nsec[8] btime_sec[8] btime_nsec[8] \
1019*134e1779SJakub Wojciech Klama          gen[8] data_version[8]
1020*134e1779SJakub Wojciech Klama
1021*134e1779SJakub Wojciech Klama    getattr gets attributes of a file system object referenced by fid.
1022*134e1779SJakub Wojciech Klama    The response is intended to follow pretty closely the fields
1023*134e1779SJakub Wojciech Klama    returned by the stat(2) system call:
1024*134e1779SJakub Wojciech Klama
1025*134e1779SJakub Wojciech Klama    struct stat {
1026*134e1779SJakub Wojciech Klama        dev_t     st_dev;     /* ID of device containing file */
1027*134e1779SJakub Wojciech Klama        ino_t     st_ino;     /* inode number */
1028*134e1779SJakub Wojciech Klama        mode_t    st_mode;    /* protection */
1029*134e1779SJakub Wojciech Klama        nlink_t   st_nlink;   /* number of hard links */
1030*134e1779SJakub Wojciech Klama        uid_t     st_uid;     /* user ID of owner */
1031*134e1779SJakub Wojciech Klama        gid_t     st_gid;     /* group ID of owner */
1032*134e1779SJakub Wojciech Klama        dev_t     st_rdev;    /* device ID (if special file) */
1033*134e1779SJakub Wojciech Klama        off_t     st_size;    /* total size, in bytes */
1034*134e1779SJakub Wojciech Klama        blksize_t st_blksize; /* blocksize for file system I/O */
1035*134e1779SJakub Wojciech Klama        blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
1036*134e1779SJakub Wojciech Klama        time_t    st_atime;   /* time of last access */
1037*134e1779SJakub Wojciech Klama        time_t    st_mtime;   /* time of last modification */
1038*134e1779SJakub Wojciech Klama        time_t    st_ctime;   /* time of last status change */
1039*134e1779SJakub Wojciech Klama    };
1040*134e1779SJakub Wojciech Klama
1041*134e1779SJakub Wojciech Klama    The differences are:
1042*134e1779SJakub Wojciech Klama
1043*134e1779SJakub Wojciech Klama     * st_dev is omitted
1044*134e1779SJakub Wojciech Klama     * st_ino is contained in the path component of qid
1045*134e1779SJakub Wojciech Klama     * times are nanosecond resolution
1046*134e1779SJakub Wojciech Klama     * btime, gen and data_version fields are reserved for future use
1047*134e1779SJakub Wojciech Klama
1048*134e1779SJakub Wojciech Klama    Not all fields are valid in every call. request_mask is a bitmask
1049*134e1779SJakub Wojciech Klama    indicating which fields are requested. valid is a bitmask
1050*134e1779SJakub Wojciech Klama    indicating which fields are valid in the response. The mask
1051*134e1779SJakub Wojciech Klama    values are as follows:
1052*134e1779SJakub Wojciech Klama
1053*134e1779SJakub Wojciech Klama    #define GETATTR_MODE        0x00000001
1054*134e1779SJakub Wojciech Klama    #define GETATTR_NLINK       0x00000002
1055*134e1779SJakub Wojciech Klama    #define GETATTR_UID         0x00000004
1056*134e1779SJakub Wojciech Klama    #define GETATTR_GID         0x00000008
1057*134e1779SJakub Wojciech Klama    #define GETATTR_RDEV        0x00000010
1058*134e1779SJakub Wojciech Klama    #define GETATTR_ATIME       0x00000020
1059*134e1779SJakub Wojciech Klama    #define GETATTR_MTIME       0x00000040
1060*134e1779SJakub Wojciech Klama    #define GETATTR_CTIME       0x00000080
1061*134e1779SJakub Wojciech Klama    #define GETATTR_INO         0x00000100
1062*134e1779SJakub Wojciech Klama    #define GETATTR_SIZE        0x00000200
1063*134e1779SJakub Wojciech Klama    #define GETATTR_BLOCKS      0x00000400
1064*134e1779SJakub Wojciech Klama
1065*134e1779SJakub Wojciech Klama    #define GETATTR_BTIME       0x00000800
1066*134e1779SJakub Wojciech Klama    #define GETATTR_GEN         0x00001000
1067*134e1779SJakub Wojciech Klama    #define GETATTR_DATA_VERSION 0x00002000
1068*134e1779SJakub Wojciech Klama
1069*134e1779SJakub Wojciech Klama    #define GETATTR_BASIC       0x000007ff  /* Mask for fields up to BLOCKS */
1070*134e1779SJakub Wojciech Klama    #define GETATTR_ALL         0x00003fff  /* Mask for All fields above */
1071*134e1779SJakub Wojciech Klama
1072*134e1779SJakub Wojciech KlamaTsetattr.L = 26: tag[2] fid[4] valid[4] mode[4] uid[4] gid[4] size[8] \
1073*134e1779SJakub Wojciech Klama               atime_sec[8] atime_nsec[8] mtime_sec[8] mtime_nsec[8]
1074*134e1779SJakub Wojciech KlamaRsetattr.L: tag[2]
1075*134e1779SJakub Wojciech Klama    setattr sets attributes of a file system object referenced by
1076*134e1779SJakub Wojciech Klama    fid.  As with getattr, valid is a bitmask selecting which
1077*134e1779SJakub Wojciech Klama    fields to set, which can be any combination of:
1078*134e1779SJakub Wojciech Klama
1079*134e1779SJakub Wojciech Klama    mode - Linux chmod(2) mode bits.
1080*134e1779SJakub Wojciech Klama
1081*134e1779SJakub Wojciech Klama    uid, gid - New owner, group of the file as described in Linux chown(2).
1082*134e1779SJakub Wojciech Klama
1083*134e1779SJakub Wojciech Klama    size - New file size as handled by Linux truncate(2).
1084*134e1779SJakub Wojciech Klama
1085*134e1779SJakub Wojciech Klama    atime_sec, atime_nsec - Time of last file access.
1086*134e1779SJakub Wojciech Klama
1087*134e1779SJakub Wojciech Klama    mtime_sec, mtime_nsec - Time of last file modification.
1088*134e1779SJakub Wojciech Klama
1089*134e1779SJakub Wojciech Klama    The valid bits are defined as follows:
1090*134e1779SJakub Wojciech Klama
1091*134e1779SJakub Wojciech Klama    #define SETATTR_MODE        0x00000001
1092*134e1779SJakub Wojciech Klama    #define SETATTR_UID         0x00000002
1093*134e1779SJakub Wojciech Klama    #define SETATTR_GID         0x00000004
1094*134e1779SJakub Wojciech Klama    #define SETATTR_SIZE        0x00000008
1095*134e1779SJakub Wojciech Klama    #define SETATTR_ATIME       0x00000010
1096*134e1779SJakub Wojciech Klama    #define SETATTR_MTIME       0x00000020
1097*134e1779SJakub Wojciech Klama    #define SETATTR_CTIME       0x00000040
1098*134e1779SJakub Wojciech Klama    #define SETATTR_ATIME_SET   0x00000080
1099*134e1779SJakub Wojciech Klama    #define SETATTR_MTIME_SET   0x00000100
1100*134e1779SJakub Wojciech Klama
1101*134e1779SJakub Wojciech Klama    If a time bit is set without the corresponding SET bit, the
1102*134e1779SJakub Wojciech Klama    current system time on the server is used instead of the value
1103*134e1779SJakub Wojciech Klama    sent in the request.
1104*134e1779SJakub Wojciech Klama
1105*134e1779SJakub Wojciech KlamaTxattrwalk.L = 30: tag[2] fid[4] newfid[4] name[s]
1106*134e1779SJakub Wojciech KlamaRxattrwalk.L: tag[2] size[8]
1107*134e1779SJakub Wojciech Klama    xattrwalk gets a newfid pointing to xattr name.  This fid can
1108*134e1779SJakub Wojciech Klama    later be used to read the xattr value.  If name is NULL newfid
1109*134e1779SJakub Wojciech Klama    can be used to get the list of extended attributes associated
1110*134e1779SJakub Wojciech Klama    with the file system object.
1111*134e1779SJakub Wojciech Klama
1112*134e1779SJakub Wojciech KlamaTxattrcreate.L = 32: tag[2] fid[4] name[s] attr_size[8] flags[4]
1113*134e1779SJakub Wojciech KlamaRxattrcreate.L: tag[2]
1114*134e1779SJakub Wojciech Klama    xattrcreate gets a fid pointing to the xattr name.  This fid
1115*134e1779SJakub Wojciech Klama    can later be used to set the xattr value.
1116*134e1779SJakub Wojciech Klama
1117*134e1779SJakub Wojciech Klama    flag is derived from set Linux setxattr. The manpage says
1118*134e1779SJakub Wojciech Klama
1119*134e1779SJakub Wojciech Klama        The flags parameter can be used to refine the semantics of
1120*134e1779SJakub Wojciech Klama        the operation.  XATTR_CREATE specifies a pure create,
1121*134e1779SJakub Wojciech Klama        which fails if the named attribute exists already.
1122*134e1779SJakub Wojciech Klama        XATTR_REPLACE specifies a pure replace operation, which
1123*134e1779SJakub Wojciech Klama        fails if the named attribute does not already exist.  By
1124*134e1779SJakub Wojciech Klama        default (no flags), the extended attribute will be created
1125*134e1779SJakub Wojciech Klama        if need be, or will simply replace the value if the
1126*134e1779SJakub Wojciech Klama        attribute exists.
1127*134e1779SJakub Wojciech Klama
1128*134e1779SJakub Wojciech Klama    The actual setxattr operation happens when the fid is clunked.
1129*134e1779SJakub Wojciech Klama    At that point the written byte count and the attr_size
1130*134e1779SJakub Wojciech Klama    specified in TXATTRCREATE should be same otherwise an error
1131*134e1779SJakub Wojciech Klama    will be returned.
1132*134e1779SJakub Wojciech Klama
1133*134e1779SJakub Wojciech KlamaTreaddir.L = 40: tag[2] fid[4] offset[8] count[4]
1134*134e1779SJakub Wojciech KlamaRreaddir.L: tag[2] count[4] data[count]
1135*134e1779SJakub Wojciech Klama    readdir requests that the server return directory entries from
1136*134e1779SJakub Wojciech Klama    the directory represented by fid, previously opened with
1137*134e1779SJakub Wojciech Klama    lopen.  offset is zero on the first call.
1138*134e1779SJakub Wojciech Klama
1139*134e1779SJakub Wojciech Klama    Directory entries are represented as variable-length records:
1140*134e1779SJakub Wojciech Klama        qid[qid] offset[8] type[1] name[s]
1141*134e1779SJakub Wojciech Klama    At most count bytes will be returned in data.  If count is not
1142*134e1779SJakub Wojciech Klama    zero in the response, more data is available.  On subsequent
1143*134e1779SJakub Wojciech Klama    calls, offset is the offset returned in the last directory
1144*134e1779SJakub Wojciech Klama    entry of the previous call.
1145*134e1779SJakub Wojciech Klama
1146*134e1779SJakub Wojciech KlamaTfsync.L = 50: tag[2] fid[4]
1147*134e1779SJakub Wojciech KlamaRfsync.L: tag[2]
1148*134e1779SJakub Wojciech Klama    fsync tells the server to flush any cached data associated
1149*134e1779SJakub Wojciech Klama    with fid, previously opened with lopen.
1150*134e1779SJakub Wojciech Klama
1151*134e1779SJakub Wojciech KlamaTlock.L = 52: tag[2] fid[4] type[1] flags[4] start[8] length[8] \
1152*134e1779SJakub Wojciech Klama       proc_id[4] client_id[s]
1153*134e1779SJakub Wojciech KlamaRlock.L: tag[2] status[1]
1154*134e1779SJakub Wojciech Klama    lock is used to acquire or release a POSIX record lock on fid
1155*134e1779SJakub Wojciech Klama    and has semantics similar to Linux fcntl(F_SETLK).
1156*134e1779SJakub Wojciech Klama
1157*134e1779SJakub Wojciech Klama    type has one of the values:
1158*134e1779SJakub Wojciech Klama
1159*134e1779SJakub Wojciech Klama        #define LOCK_TYPE_RDLCK 0
1160*134e1779SJakub Wojciech Klama        #define LOCK_TYPE_WRLCK 1
1161*134e1779SJakub Wojciech Klama        #define LOCK_TYPE_UNLCK 2
1162*134e1779SJakub Wojciech Klama
1163*134e1779SJakub Wojciech Klama    start, length, and proc_id correspond to the analagous fields
1164*134e1779SJakub Wojciech Klama    passed to Linux fcntl(F_SETLK):
1165*134e1779SJakub Wojciech Klama
1166*134e1779SJakub Wojciech Klama    struct flock {
1167*134e1779SJakub Wojciech Klama        short l_type;  /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */
1168*134e1779SJakub Wojciech Klama        short l_whence;/* How to intrprt l_start: SEEK_SET,SEEK_CUR,SEEK_END */
1169*134e1779SJakub Wojciech Klama        off_t l_start; /* Starting offset for lock */
1170*134e1779SJakub Wojciech Klama        off_t l_len;   /* Number of bytes to lock */
1171*134e1779SJakub Wojciech Klama        pid_t l_pid;   /* PID of process blocking our lock (F_GETLK only) */
1172*134e1779SJakub Wojciech Klama    };
1173*134e1779SJakub Wojciech Klama
1174*134e1779SJakub Wojciech Klama    flags bits are:
1175*134e1779SJakub Wojciech Klama
1176*134e1779SJakub Wojciech Klama        #define LOCK_SUCCESS    0
1177*134e1779SJakub Wojciech Klama        #define LOCK_BLOCKED    1
1178*134e1779SJakub Wojciech Klama        #define LOCK_ERROR      2
1179*134e1779SJakub Wojciech Klama        #define LOCK_GRACE      3
1180*134e1779SJakub Wojciech Klama
1181*134e1779SJakub Wojciech Klama    The Linux v9fs client implements the fcntl(F_SETLKW)
1182*134e1779SJakub Wojciech Klama    (blocking) lock request by calling lock with
1183*134e1779SJakub Wojciech Klama    LOCK_FLAGS_BLOCK set.  If the response is LOCK_BLOCKED,
1184*134e1779SJakub Wojciech Klama    it retries the lock request in an interruptible loop until
1185*134e1779SJakub Wojciech Klama    status is no longer LOCK_BLOCKED.
1186*134e1779SJakub Wojciech Klama
1187*134e1779SJakub Wojciech Klama    The Linux v9fs client translates BSD advisory locks (flock) to
1188*134e1779SJakub Wojciech Klama    whole-file POSIX record locks.  v9fs does not implement
1189*134e1779SJakub Wojciech Klama    mandatory locks and will return ENOLCK if use is attempted.
1190*134e1779SJakub Wojciech Klama
1191*134e1779SJakub Wojciech Klama    Because of POSIX record lock inheritance and upgrade
1192*134e1779SJakub Wojciech Klama    properties, pass-through servers must be implemented
1193*134e1779SJakub Wojciech Klama    carefully.
1194*134e1779SJakub Wojciech Klama
1195*134e1779SJakub Wojciech KlamaTgetlock.L = 54: tag[2] fid[4] type[1] start[8] length[8] proc_id[4] \
1196*134e1779SJakub Wojciech Klama               client_id[s]
1197*134e1779SJakub Wojciech KlamaRgetlock.L: tag[2] type[1] start[8] length[8] proc_id[4] client_id[s]
1198*134e1779SJakub Wojciech Klama    getlock tests for the existence of a POSIX record lock and has
1199*134e1779SJakub Wojciech Klama    semantics similar to Linux fcntl(F_GETLK).
1200*134e1779SJakub Wojciech Klama
1201*134e1779SJakub Wojciech Klama    As with lock, type has one of the values defined above, and
1202*134e1779SJakub Wojciech Klama    start, length, and proc_id correspond to the analagous fields
1203*134e1779SJakub Wojciech Klama    in struct flock passed to Linux fcntl(F_GETLK), and client_Id
1204*134e1779SJakub Wojciech Klama    is an additional mechanism for uniquely identifying the lock
1205*134e1779SJakub Wojciech Klama    requester and is set to the nodename by the Linux v9fs client.
1206*134e1779SJakub Wojciech Klama
1207*134e1779SJakub Wojciech KlamaTlink.L = 70: tag[2] dfid[4] fid[4] name[s]
1208*134e1779SJakub Wojciech KlamaRlink.L: tag[2]
1209*134e1779SJakub Wojciech Klama    link creates a hard link name in directory dfid.  The link
1210*134e1779SJakub Wojciech Klama    target is referenced by fid.
1211*134e1779SJakub Wojciech Klama
1212*134e1779SJakub Wojciech KlamaTmkdir.L = 72: tag[2] dfid[4] name[s] mode[4] gid[4]
1213*134e1779SJakub Wojciech KlamaRmkdir.L: tag[2] qid[qid]
1214*134e1779SJakub Wojciech Klama    mkdir creates a new directory name in parent directory dfid.
1215*134e1779SJakub Wojciech Klama
1216*134e1779SJakub Wojciech Klama    mode contains Linux mkdir(2) mode bits.
1217*134e1779SJakub Wojciech Klama
1218*134e1779SJakub Wojciech Klama    gid is the effective group ID of the caller.
1219*134e1779SJakub Wojciech Klama
1220*134e1779SJakub Wojciech Klama    The qid of the new directory is returned in the response.
1221*134e1779SJakub Wojciech Klama
1222*134e1779SJakub Wojciech KlamaTrenameat.L = 74: tag[2] olddirfid[4] oldname[s] newdirfid[4] newname[s]
1223*134e1779SJakub Wojciech KlamaRrenameat.L: tag[2]
1224*134e1779SJakub Wojciech Klama    Change the name of a file from oldname to newname, possible
1225*134e1779SJakub Wojciech Klama    moving it from old directory represented by olddirfid to new
1226*134e1779SJakub Wojciech Klama    directory represented by newdirfid.
1227*134e1779SJakub Wojciech Klama
1228*134e1779SJakub Wojciech Klama    If the server returns ENOTSUPP, the client should fall back to
1229*134e1779SJakub Wojciech Klama    the rename operation.
1230*134e1779SJakub Wojciech Klama
1231*134e1779SJakub Wojciech KlamaTunlinkat.L = 76: tag[2] dirfd[4] name[s] flags[4]
1232*134e1779SJakub Wojciech KlamaRunlinkat.L: tag[2]
1233*134e1779SJakub Wojciech Klama    Unlink name from directory represented by dirfd.  If the file
1234*134e1779SJakub Wojciech Klama    is represented by a fid, that fid is not clunked.  If the
1235*134e1779SJakub Wojciech Klama    server returns ENOTSUPP, the client should fall back to the
1236*134e1779SJakub Wojciech Klama    remove operation.
1237*134e1779SJakub Wojciech Klama
1238*134e1779SJakub Wojciech Klama    There seems to be only one defined flag:
1239*134e1779SJakub Wojciech Klama
1240*134e1779SJakub Wojciech Klama        #define AT_REMOVEDIR    0x200
1241*134e1779SJakub Wojciech Klama
1242*134e1779SJakub Wojciech KlamaTversion = 100: tag[2] msize[4] version[s]:auto
1243*134e1779SJakub Wojciech KlamaRversion: tag[2] msize[4] version[s]
1244*134e1779SJakub Wojciech Klama
1245*134e1779SJakub Wojciech Klama    negotiate protocol version
1246*134e1779SJakub Wojciech Klama
1247*134e1779SJakub Wojciech Klama    version establishes the msize, which is the maximum message
1248*134e1779SJakub Wojciech Klama    size inclusive of the size value that can be handled by both
1249*134e1779SJakub Wojciech Klama    client and server.
1250*134e1779SJakub Wojciech Klama
1251*134e1779SJakub Wojciech Klama    It also establishes the protocol version.  For 9P2000.L
1252*134e1779SJakub Wojciech Klama    version must be the string 9P2000.L.
1253*134e1779SJakub Wojciech Klama
1254*134e1779SJakub Wojciech KlamaTauth = 102: tag[2] afid[4] uname[s] aname[s] n_uname[4]
1255*134e1779SJakub Wojciech KlamaRauth: tag[2] aqid[qid]
1256*134e1779SJakub Wojciech Klama    auth initiates an authentication handshake for n_uname.
1257*134e1779SJakub Wojciech Klama    Rlerror is returned if authentication is not required.  If
1258*134e1779SJakub Wojciech Klama    successful, afid is used to read/write the authentication
1259*134e1779SJakub Wojciech Klama    handshake (protocol does not specify what is read/written),
1260*134e1779SJakub Wojciech Klama    and afid is presented in the attach.
1261*134e1779SJakub Wojciech Klama
1262*134e1779SJakub Wojciech KlamaTattach = 104: tag[2] fid[4] afid[4] uname[s] aname[s] {.u: n_uname[4] }
1263*134e1779SJakub Wojciech KlamaRattach: tag[2] qid[qid]
1264*134e1779SJakub Wojciech Klama    attach introduces a new user to the server, and establishes
1265*134e1779SJakub Wojciech Klama    fid as the root for that user on the file tree selected by
1266*134e1779SJakub Wojciech Klama    aname.
1267*134e1779SJakub Wojciech Klama
1268*134e1779SJakub Wojciech Klama    afid can be NOFID (~0) or the fid from a previous auth
1269*134e1779SJakub Wojciech Klama    handshake.  The afid can be clunked immediately after the
1270*134e1779SJakub Wojciech Klama    attach.
1271*134e1779SJakub Wojciech Klama
1272*134e1779SJakub Wojciech Klama        #define NOFID       0xffffffff
1273*134e1779SJakub Wojciech Klama
1274*134e1779SJakub Wojciech Klama    n_uname, if not set to NONUNAME (~0), is the uid of the
1275*134e1779SJakub Wojciech Klama    user and is used in preference to uname.  Note that it appears
1276*134e1779SJakub Wojciech Klama    in both .u and .L (unlike most .u-specific features).
1277*134e1779SJakub Wojciech Klama
1278*134e1779SJakub Wojciech Klama        #define NONUNAME    0xffffffff
1279*134e1779SJakub Wojciech Klama
1280*134e1779SJakub Wojciech Klama    v9fs has several modes of access which determine how it uses
1281*134e1779SJakub Wojciech Klama    attach.  In the default access=user, an initial attach is sent
1282*134e1779SJakub Wojciech Klama    for the user provided in the uname=name mount option, and for
1283*134e1779SJakub Wojciech Klama    each user that accesses the file system thereafter.  For
1284*134e1779SJakub Wojciech Klama    access=, only the initial attach is sent for and all other
1285*134e1779SJakub Wojciech Klama    users are denied access by the client.
1286*134e1779SJakub Wojciech Klama
1287*134e1779SJakub Wojciech KlamaRerror = 107: tag[2] errstr[s] {.u: errnum[4] }
1288*134e1779SJakub Wojciech Klama
1289*134e1779SJakub Wojciech KlamaTflush = 108: tag[2] oldtag[2]
1290*134e1779SJakub Wojciech KlamaRflush: tag[2]
1291*134e1779SJakub Wojciech Klama    flush aborts an in-flight request referenced by oldtag, if any.
1292*134e1779SJakub Wojciech Klama
1293*134e1779SJakub Wojciech KlamaTwalk = 110: tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s])
1294*134e1779SJakub Wojciech KlamaRwalk: tag[2] nwqid[2] nwqid*(wqid[qid])
1295*134e1779SJakub Wojciech Klama    walk is used to descend a directory represented by fid using
1296*134e1779SJakub Wojciech Klama    successive path elements provided in the wname array.  If
1297*134e1779SJakub Wojciech Klama    succesful, newfid represents the new path.
1298*134e1779SJakub Wojciech Klama
1299*134e1779SJakub Wojciech Klama    fid can be cloned to newfid by calling walk with nwname set to
1300*134e1779SJakub Wojciech Klama    zero.
1301*134e1779SJakub Wojciech Klama
1302*134e1779SJakub Wojciech Klama    if nwname==0, fid need not represent a directory.
1303*134e1779SJakub Wojciech Klama
1304*134e1779SJakub Wojciech KlamaTopen = 112: tag[2] fid[4] mode[1]
1305*134e1779SJakub Wojciech KlamaRopen: tag[2] qid[qid] iounit[4]
1306*134e1779SJakub Wojciech Klama    open prepares fid for file (or directory) I/O.
1307*134e1779SJakub Wojciech Klama
1308*134e1779SJakub Wojciech Klama    mode is:
1309*134e1779SJakub Wojciech Klama        #define OREAD       0   /* open for read */
1310*134e1779SJakub Wojciech Klama        #define OWRITE      1   /* open for write */
1311*134e1779SJakub Wojciech Klama        #define ORDWR       2   /* open for read and write */
1312*134e1779SJakub Wojciech Klama        #define OEXEC       3   /* open for execute */
1313*134e1779SJakub Wojciech Klama
1314*134e1779SJakub Wojciech Klama        #define OTRUNC      16  /* truncate (illegal if OEXEC) */
1315*134e1779SJakub Wojciech Klama        #define OCEXEC      32  /* close on exec (nonsensical) */
1316*134e1779SJakub Wojciech Klama        #define ORCLOSE     64  /* remove on close */
1317*134e1779SJakub Wojciech Klama        #define ODIRECT     128 /* direct access (.u extension?) */
1318*134e1779SJakub Wojciech Klama
1319*134e1779SJakub Wojciech KlamaTcreate = 114: tag[2] fid[4] name[s] perm[4] mode[1] {.u: extension[s] }
1320*134e1779SJakub Wojciech KlamaRcreate: tag[2] qid[qid] iounit[4]
1321*134e1779SJakub Wojciech Klama    create is similar to open; however, the incoming fid is the
1322*134e1779SJakub Wojciech Klama    diretory in which the file is to be created, and on success,
1323*134e1779SJakub Wojciech Klama    return, the fid refers to the then-created file.
1324*134e1779SJakub Wojciech Klama
1325*134e1779SJakub Wojciech KlamaTread = 116: tag[2] fid[4] offset[8] count[4]
1326*134e1779SJakub Wojciech KlamaRread: tag[2] count[4] data[count]
1327*134e1779SJakub Wojciech Klama    perform a read on the file represented by fid.  Note that in
1328*134e1779SJakub Wojciech Klama    v9fs, a read(2) or write(2) system call for a chunk of the
1329*134e1779SJakub Wojciech Klama    file that won't fit in a single request is broken up into
1330*134e1779SJakub Wojciech Klama    multiple requests.
1331*134e1779SJakub Wojciech Klama
1332*134e1779SJakub Wojciech Klama    Under 9P2000.L, read cannot be used on directories.  See readdir.
1333*134e1779SJakub Wojciech Klama
1334*134e1779SJakub Wojciech KlamaTwrite = 118: tag[2] fid[4] offset[8] count[4] data[count]
1335*134e1779SJakub Wojciech KlamaRwrite: tag[2] count[4]
1336*134e1779SJakub Wojciech Klama    perform a write on the file represented by fid.  Note that in
1337*134e1779SJakub Wojciech Klama    v9fs, a read(2) or write(2) system call for a chunk of the
1338*134e1779SJakub Wojciech Klama    file that won't fit in a single request is broken up into
1339*134e1779SJakub Wojciech Klama    multiple requests.
1340*134e1779SJakub Wojciech Klama
1341*134e1779SJakub Wojciech Klama    write cannot be used on directories.
1342*134e1779SJakub Wojciech Klama
1343*134e1779SJakub Wojciech KlamaTclunk = 120: tag[2] fid[4]
1344*134e1779SJakub Wojciech KlamaRclunk: tag[2]
1345*134e1779SJakub Wojciech Klama    clunk signifies that fid is no longer needed by the client.
1346*134e1779SJakub Wojciech Klama
1347*134e1779SJakub Wojciech KlamaTremove = 122: tag[2] fid[4]
1348*134e1779SJakub Wojciech KlamaRremove: tag[2]
1349*134e1779SJakub Wojciech Klama    remove removes the file system object represented by fid.
1350*134e1779SJakub Wojciech Klama
1351*134e1779SJakub Wojciech Klama    The fid is always clunked (even on error).
1352*134e1779SJakub Wojciech Klama
1353*134e1779SJakub Wojciech KlamaTstat = 124: tag[2] fid[4]
1354*134e1779SJakub Wojciech KlamaRstat: tag[2] size[2] data[size]
1355*134e1779SJakub Wojciech Klama
1356*134e1779SJakub Wojciech KlamaTwstat = 126: tag[2] fid[4] size[2] data[size]
1357*134e1779SJakub Wojciech KlamaRwstat: tag[2]
1358*134e1779SJakub Wojciech Klama"""
1359*134e1779SJakub Wojciech Klama
1360*134e1779SJakub Wojciech Klamaclass _Token(object):
1361*134e1779SJakub Wojciech Klama    r"""
1362*134e1779SJakub Wojciech Klama    A scanned token.
1363*134e1779SJakub Wojciech Klama
1364*134e1779SJakub Wojciech Klama    Tokens have a type (tok.ttype) and value (tok.value).  The value
1365*134e1779SJakub Wojciech Klama    is generally the token itself, although sometimes a prefix and/or
1366*134e1779SJakub Wojciech Klama    suffix has been removed (for 'label', 'word*', ':aux', and
1367*134e1779SJakub Wojciech Klama    '[type]' tokens).  If prefix and/or suffix are removed, the full
1368*134e1779SJakub Wojciech Klama    original token is
1369*134e1779SJakub Wojciech Klama    in its .orig.
1370*134e1779SJakub Wojciech Klama
1371*134e1779SJakub Wojciech Klama    Tokens are:
1372*134e1779SJakub Wojciech Klama     - 'word', 'word*', or 'label':
1373*134e1779SJakub Wojciech Klama         '[.\w]+' followed by optional '*' or ':':
1374*134e1779SJakub Wojciech Klama
1375*134e1779SJakub Wojciech Klama     - 'aux': ':' followed by '\w+' (used for :auto annotation)
1376*134e1779SJakub Wojciech Klama
1377*134e1779SJakub Wojciech Klama     - 'type':
1378*134e1779SJakub Wojciech Klama       open bracket '[', followed by '\w+' or '\d+' (only one of these),
1379*134e1779SJakub Wojciech Klama       followed by close bracket ']'
1380*134e1779SJakub Wojciech Klama
1381*134e1779SJakub Wojciech Klama     - '(', ')', '{', '}': themeselves
1382*134e1779SJakub Wojciech Klama
1383*134e1779SJakub Wojciech Klama    Each token can have arbitrary leading white space (which is
1384*134e1779SJakub Wojciech Klama    discarded).
1385*134e1779SJakub Wojciech Klama
1386*134e1779SJakub Wojciech Klama    (Probably should return ':' as a char and handle it in parser,
1387*134e1779SJakub Wojciech Klama    but oh well.)
1388*134e1779SJakub Wojciech Klama    """
1389*134e1779SJakub Wojciech Klama    def __init__(self, ttype, value, orig=None):
1390*134e1779SJakub Wojciech Klama        self.ttype = ttype
1391*134e1779SJakub Wojciech Klama        self.value = value
1392*134e1779SJakub Wojciech Klama        self.orig = value if orig is None else orig
1393*134e1779SJakub Wojciech Klama        if self.ttype == 'type' and self.value.isdigit():
1394*134e1779SJakub Wojciech Klama            self.ival = int(self.value)
1395*134e1779SJakub Wojciech Klama        else:
1396*134e1779SJakub Wojciech Klama            self.ival = None
1397*134e1779SJakub Wojciech Klama    def __str__(self):
1398*134e1779SJakub Wojciech Klama        return self.orig
1399*134e1779SJakub Wojciech Klama
1400*134e1779SJakub Wojciech Klama_Token.tok_expr = re.compile(r'\s*([.\w]+(?:\*|:)?'
1401*134e1779SJakub Wojciech Klama                             r'|:\w+'
1402*134e1779SJakub Wojciech Klama                             r'|\[(?:\w+|\d+)\]'
1403*134e1779SJakub Wojciech Klama                             r'|[(){}])')
1404*134e1779SJakub Wojciech Klama
1405*134e1779SJakub Wojciech Klamadef _scan(string):
1406*134e1779SJakub Wojciech Klama    """
1407*134e1779SJakub Wojciech Klama    Tokenize a string.
1408*134e1779SJakub Wojciech Klama
1409*134e1779SJakub Wojciech Klama    Note: This raises a ValueError with the position of any unmatched
1410*134e1779SJakub Wojciech Klama    character in the string.
1411*134e1779SJakub Wojciech Klama    """
1412*134e1779SJakub Wojciech Klama    tlist = []
1413*134e1779SJakub Wojciech Klama
1414*134e1779SJakub Wojciech Klama    # make sure entire string is tokenized properly
1415*134e1779SJakub Wojciech Klama    pos = 0
1416*134e1779SJakub Wojciech Klama    for item in _Token.tok_expr.finditer(string):
1417*134e1779SJakub Wojciech Klama        span = item.span()
1418*134e1779SJakub Wojciech Klama        if span[0] != pos:
1419*134e1779SJakub Wojciech Klama            print('error: unmatched character(s) in input\n{0}\n{1}^'.format(
1420*134e1779SJakub Wojciech Klama                string, ' ' * pos))
1421*134e1779SJakub Wojciech Klama            raise ValueError('unmatched lexeme', pos)
1422*134e1779SJakub Wojciech Klama        pos = span[1]
1423*134e1779SJakub Wojciech Klama        tlist.append(item.group(1))
1424*134e1779SJakub Wojciech Klama    if pos != len(string):
1425*134e1779SJakub Wojciech Klama        print('error: unmatched character(s) in input\n{0}\n{1}^'.format(
1426*134e1779SJakub Wojciech Klama            string, ' ' * pos))
1427*134e1779SJakub Wojciech Klama        raise ValueError('unmatched lexeme', pos)
1428*134e1779SJakub Wojciech Klama
1429*134e1779SJakub Wojciech Klama    # classify each token, stripping decorations
1430*134e1779SJakub Wojciech Klama    result = []
1431*134e1779SJakub Wojciech Klama    for item in tlist:
1432*134e1779SJakub Wojciech Klama        if item in ('(', ')', '{', '}'):
1433*134e1779SJakub Wojciech Klama            tok = _Token(item, item)
1434*134e1779SJakub Wojciech Klama        elif item[0] == ':':
1435*134e1779SJakub Wojciech Klama            tok = _Token('aux', item[1:], item)
1436*134e1779SJakub Wojciech Klama        elif item.endswith(':'):
1437*134e1779SJakub Wojciech Klama            tok = _Token('label', item[0:-1], item)
1438*134e1779SJakub Wojciech Klama        elif item.endswith('*'):
1439*134e1779SJakub Wojciech Klama            tok = _Token('word*', item[0:-1], item)
1440*134e1779SJakub Wojciech Klama        elif item[0] == '[':
1441*134e1779SJakub Wojciech Klama            # integer or named type
1442*134e1779SJakub Wojciech Klama            if item[-1] != ']':
1443*134e1779SJakub Wojciech Klama                raise ValueError('internal error: "{0}" is not [...]'.format(
1444*134e1779SJakub Wojciech Klama                    item))
1445*134e1779SJakub Wojciech Klama            tok = _Token('type', item[1:-1], item)
1446*134e1779SJakub Wojciech Klama        else:
1447*134e1779SJakub Wojciech Klama            tok = _Token('word', item)
1448*134e1779SJakub Wojciech Klama        result.append(tok)
1449*134e1779SJakub Wojciech Klama    return result
1450*134e1779SJakub Wojciech Klama
1451*134e1779SJakub Wojciech Klamadef _debug_print_sequencer(seq):
1452*134e1779SJakub Wojciech Klama    """for debugging"""
1453*134e1779SJakub Wojciech Klama    print('sequencer is {0!r}'.format(seq), file=sys.stderr)
1454*134e1779SJakub Wojciech Klama    for i, enc in enumerate(seq):
1455*134e1779SJakub Wojciech Klama        print(' [{0:d}] = {1}'.format(i, enc), file=sys.stderr)
1456*134e1779SJakub Wojciech Klama
1457*134e1779SJakub Wojciech Klamadef _parse_expr(seq, string, typedefs):
1458*134e1779SJakub Wojciech Klama    """
1459*134e1779SJakub Wojciech Klama    Parse "expression-ish" items, which is a list of:
1460*134e1779SJakub Wojciech Klama        name[type]
1461*134e1779SJakub Wojciech Klama        name*(subexpr)    (a literal asterisk)
1462*134e1779SJakub Wojciech Klama        { label ... }
1463*134e1779SJakub Wojciech Klama
1464*134e1779SJakub Wojciech Klama    The "type" may be an integer or a second name.  In the case
1465*134e1779SJakub Wojciech Klama    of a second name it must be something from <typedefs>.
1466*134e1779SJakub Wojciech Klama
1467*134e1779SJakub Wojciech Klama    The meaning of name[integer] is that we are going to encode
1468*134e1779SJakub Wojciech Klama    or decode a fixed-size field of <integer> bytes, using the
1469*134e1779SJakub Wojciech Klama    given name.
1470*134e1779SJakub Wojciech Klama
1471*134e1779SJakub Wojciech Klama    For name[name2], we can look up name2 in our typedefs table.
1472*134e1779SJakub Wojciech Klama    The only real typedefs's used here are "stat" and "s"; each
1473*134e1779SJakub Wojciech Klama    of these expands to a variable-size encode/decode.  See the
1474*134e1779SJakub Wojciech Klama    special case below, though.
1475*134e1779SJakub Wojciech Klama
1476*134e1779SJakub Wojciech Klama    The meaning of name*(...) is: the earlier name will have been
1477*134e1779SJakub Wojciech Klama    defined by an earlier _parse_expr for this same line.  That
1478*134e1779SJakub Wojciech Klama    earlier name provides a repeat-count.
1479*134e1779SJakub Wojciech Klama
1480*134e1779SJakub Wojciech Klama    Inside the parens we get a name[type] sub-expressino.  This may
1481*134e1779SJakub Wojciech Klama    not recurse further, so we can use a pretty cheesy parser.
1482*134e1779SJakub Wojciech Klama
1483*134e1779SJakub Wojciech Klama    As a special case, given name[name2], we first check whether
1484*134e1779SJakub Wojciech Klama    name2 is an earlier name a la name*(...).  Here the meaning
1485*134e1779SJakub Wojciech Klama    is much like name2*(name[1]), except that the result is a
1486*134e1779SJakub Wojciech Klama    simple byte string, rather than an array.
1487*134e1779SJakub Wojciech Klama
1488*134e1779SJakub Wojciech Klama    The meaning of "{ label ... " is that everything following up
1489*134e1779SJakub Wojciech Klama    to "}" is optional and used only with 9P2000.u and/or 9P2000.L.
1490*134e1779SJakub Wojciech Klama    Inside the {...} pair is the usual set of tokens, but again
1491*134e1779SJakub Wojciech Klama    {...} cannot recurse.
1492*134e1779SJakub Wojciech Klama
1493*134e1779SJakub Wojciech Klama    The parse fills in a Sequencer instance, and returns a list
1494*134e1779SJakub Wojciech Klama    of the parsed names.
1495*134e1779SJakub Wojciech Klama    """
1496*134e1779SJakub Wojciech Klama    names = []
1497*134e1779SJakub Wojciech Klama    cond = None
1498*134e1779SJakub Wojciech Klama
1499*134e1779SJakub Wojciech Klama    tokens = collections.deque(_scan(string))
1500*134e1779SJakub Wojciech Klama
1501*134e1779SJakub Wojciech Klama    def get_subscripted(tokens):
1502*134e1779SJakub Wojciech Klama        """
1503*134e1779SJakub Wojciech Klama        Allows name[integer] and name1[name2] only; returns
1504*134e1779SJakub Wojciech Klama        tuple after stripping off both tokens, or returns None
1505*134e1779SJakub Wojciech Klama        and does not strip tokens.
1506*134e1779SJakub Wojciech Klama        """
1507*134e1779SJakub Wojciech Klama        if len(tokens) == 0 or tokens[0].ttype != 'word':
1508*134e1779SJakub Wojciech Klama            return None
1509*134e1779SJakub Wojciech Klama        if len(tokens) > 1 and tokens[1].ttype == 'type':
1510*134e1779SJakub Wojciech Klama            word = tokens.popleft()
1511*134e1779SJakub Wojciech Klama            return word, tokens.popleft()
1512*134e1779SJakub Wojciech Klama        return None
1513*134e1779SJakub Wojciech Klama
1514*134e1779SJakub Wojciech Klama    def lookup(name, typeinfo, aux=None):
1515*134e1779SJakub Wojciech Klama        """
1516*134e1779SJakub Wojciech Klama        Convert cond (if not None) to its .value, so that instead
1517*134e1779SJakub Wojciech Klama        of (x, '.u') we get '.u'.
1518*134e1779SJakub Wojciech Klama
1519*134e1779SJakub Wojciech Klama        Convert typeinfo to an encdec.  Typeinfo may be 1/2/4/8, or
1520*134e1779SJakub Wojciech Klama        one of our typedef names.  If it's a typedef name it will
1521*134e1779SJakub Wojciech Klama        normally correspond to an EncDecTyped, but we have one special
1522*134e1779SJakub Wojciech Klama        case for string types, and another for using an earlier-defined
1523*134e1779SJakub Wojciech Klama        variable.
1524*134e1779SJakub Wojciech Klama        """
1525*134e1779SJakub Wojciech Klama        condval = None if cond is None else cond.value
1526*134e1779SJakub Wojciech Klama        if typeinfo.ival is None:
1527*134e1779SJakub Wojciech Klama            try:
1528*134e1779SJakub Wojciech Klama                cls, sub = typedefs[typeinfo.value]
1529*134e1779SJakub Wojciech Klama            except KeyError:
1530*134e1779SJakub Wojciech Klama                raise ValueError('unknown type name {0}'.format(typeinfo))
1531*134e1779SJakub Wojciech Klama            # the type name is typeinfo.value; the corresponding
1532*134e1779SJakub Wojciech Klama            # pfod class is cls; the *variable* name is name;
1533*134e1779SJakub Wojciech Klama            # and the sub-sequence is sub.  But if cls is None
1534*134e1779SJakub Wojciech Klama            # then it's our string type.
1535*134e1779SJakub Wojciech Klama            if cls is None:
1536*134e1779SJakub Wojciech Klama                encdec = sequencer.EncDecSimple(name, _STRING_MAGIC, aux)
1537*134e1779SJakub Wojciech Klama            else:
1538*134e1779SJakub Wojciech Klama                encdec = sequencer.EncDecTyped(cls, name, sub, aux)
1539*134e1779SJakub Wojciech Klama        else:
1540*134e1779SJakub Wojciech Klama            if typeinfo.ival not in (1, 2, 4, 8):
1541*134e1779SJakub Wojciech Klama                raise ValueError('bad integer code in {0}'.format(typeinfo))
1542*134e1779SJakub Wojciech Klama            encdec = sequencer.EncDecSimple(name, typeinfo.ival, aux)
1543*134e1779SJakub Wojciech Klama        return condval, encdec
1544*134e1779SJakub Wojciech Klama
1545*134e1779SJakub Wojciech Klama    def emit_simple(name, typeinfo, aux=None):
1546*134e1779SJakub Wojciech Klama        """
1547*134e1779SJakub Wojciech Klama        Emit name[type].  We may be inside a conditional; if so
1548*134e1779SJakub Wojciech Klama        cond is not None.
1549*134e1779SJakub Wojciech Klama        """
1550*134e1779SJakub Wojciech Klama        condval, encdec = lookup(name, typeinfo, aux)
1551*134e1779SJakub Wojciech Klama        seq.append_encdec(condval, encdec)
1552*134e1779SJakub Wojciech Klama        names.append(name)
1553*134e1779SJakub Wojciech Klama
1554*134e1779SJakub Wojciech Klama    def emit_repeat(name1, name2, typeinfo):
1555*134e1779SJakub Wojciech Klama        """
1556*134e1779SJakub Wojciech Klama        Emit name1*(name2[type]).
1557*134e1779SJakub Wojciech Klama
1558*134e1779SJakub Wojciech Klama        Note that the conditional is buried in the sub-coder for
1559*134e1779SJakub Wojciech Klama        name2.  It must be passed through anyway in case the sub-
1560*134e1779SJakub Wojciech Klama        coder is only partly conditional.  If the sub-coder is
1561*134e1779SJakub Wojciech Klama        fully conditional, each sub-coding uses or produces no
1562*134e1779SJakub Wojciech Klama        bytes and hence the array itself is effectively conditional
1563*134e1779SJakub Wojciech Klama        as well (it becomes name1 * [None]).
1564*134e1779SJakub Wojciech Klama
1565*134e1779SJakub Wojciech Klama        We don't (currently) have any auxiliary data for arrays.
1566*134e1779SJakub Wojciech Klama        """
1567*134e1779SJakub Wojciech Klama        if name1 not in names:
1568*134e1779SJakub Wojciech Klama            raise ValueError('{0}*({1}[{2}]): '
1569*134e1779SJakub Wojciech Klama                             '{0} undefined'.format(name1, name2,
1570*134e1779SJakub Wojciech Klama                                                    typeinfo.value))
1571*134e1779SJakub Wojciech Klama        condval, encdec = lookup(name2, typeinfo)
1572*134e1779SJakub Wojciech Klama        encdec = sequencer.EncDecA(name1, name2, encdec)
1573*134e1779SJakub Wojciech Klama        seq.append_encdec(condval, encdec)
1574*134e1779SJakub Wojciech Klama        names.append(name2)
1575*134e1779SJakub Wojciech Klama
1576*134e1779SJakub Wojciech Klama    def emit_bytes_repeat(name1, name2):
1577*134e1779SJakub Wojciech Klama        """
1578*134e1779SJakub Wojciech Klama        Emit name1[name2], e.g., data[count].
1579*134e1779SJakub Wojciech Klama        """
1580*134e1779SJakub Wojciech Klama        condval = None if cond is None else cond.value
1581*134e1779SJakub Wojciech Klama        # Note that the two names are reversed when compared to
1582*134e1779SJakub Wojciech Klama        # count*(data[type]).  The "sub-coder" is handled directly
1583*134e1779SJakub Wojciech Klama        # by EncDecA, hence is None.
1584*134e1779SJakub Wojciech Klama        #
1585*134e1779SJakub Wojciech Klama        # As a peculiar side effect, all bytes-repeats cause the
1586*134e1779SJakub Wojciech Klama        # count itself to become automatic (to have an aux of 'len').
1587*134e1779SJakub Wojciech Klama        encdec = sequencer.EncDecA(name2, name1, None, 'len')
1588*134e1779SJakub Wojciech Klama        seq.append_encdec(condval, encdec)
1589*134e1779SJakub Wojciech Klama        names.append(name1)
1590*134e1779SJakub Wojciech Klama
1591*134e1779SJakub Wojciech Klama    supported_conditions = ('.u')
1592*134e1779SJakub Wojciech Klama    while tokens:
1593*134e1779SJakub Wojciech Klama        token = tokens.popleft()
1594*134e1779SJakub Wojciech Klama        if token.ttype == 'label':
1595*134e1779SJakub Wojciech Klama            raise ValueError('misplaced label')
1596*134e1779SJakub Wojciech Klama        if token.ttype == 'aux':
1597*134e1779SJakub Wojciech Klama            raise ValueError('misplaced auxiliary')
1598*134e1779SJakub Wojciech Klama        if token.ttype == '{':
1599*134e1779SJakub Wojciech Klama            if cond is not None:
1600*134e1779SJakub Wojciech Klama                raise ValueError('nested "{"')
1601*134e1779SJakub Wojciech Klama            if len(tokens) == 0:
1602*134e1779SJakub Wojciech Klama                raise ValueError('unclosed "{"')
1603*134e1779SJakub Wojciech Klama            cond = tokens.popleft()
1604*134e1779SJakub Wojciech Klama            if cond.ttype != 'label':
1605*134e1779SJakub Wojciech Klama                raise ValueError('"{" not followed by cond label')
1606*134e1779SJakub Wojciech Klama            if cond.value not in supported_conditions:
1607*134e1779SJakub Wojciech Klama                raise ValueError('unsupported condition "{0}"'.format(
1608*134e1779SJakub Wojciech Klama                    cond.value))
1609*134e1779SJakub Wojciech Klama            continue
1610*134e1779SJakub Wojciech Klama        if token.ttype == '}':
1611*134e1779SJakub Wojciech Klama            if cond is None:
1612*134e1779SJakub Wojciech Klama                raise ValueError('closing "}" w/o opening "{"')
1613*134e1779SJakub Wojciech Klama            cond = None
1614*134e1779SJakub Wojciech Klama            continue
1615*134e1779SJakub Wojciech Klama        if token.ttype == 'word*':
1616*134e1779SJakub Wojciech Klama            if len(tokens) == 0 or tokens[0].ttype != '(':
1617*134e1779SJakub Wojciech Klama                raise ValueError('{0} not followed by (...)'.format(token))
1618*134e1779SJakub Wojciech Klama            tokens.popleft()
1619*134e1779SJakub Wojciech Klama            repeat = get_subscripted(tokens)
1620*134e1779SJakub Wojciech Klama            if repeat is None:
1621*134e1779SJakub Wojciech Klama                raise ValueError('parse error after {0}('.format(token))
1622*134e1779SJakub Wojciech Klama            if len(tokens) == 0 or tokens[0].ttype != ')':
1623*134e1779SJakub Wojciech Klama                raise ValueError('missing ")" after {0}({1}{2}'.format(
1624*134e1779SJakub Wojciech Klama                    token, repeat[0], repeat[1]))
1625*134e1779SJakub Wojciech Klama            tokens.popleft()
1626*134e1779SJakub Wojciech Klama            # N.B.: a repeat cannot have an auxiliary info (yet?).
1627*134e1779SJakub Wojciech Klama            emit_repeat(token.value, repeat[0].value, repeat[1])
1628*134e1779SJakub Wojciech Klama            continue
1629*134e1779SJakub Wojciech Klama        if token.ttype == 'word':
1630*134e1779SJakub Wojciech Klama            # Special case: _STRING_MAGIC turns into a string
1631*134e1779SJakub Wojciech Klama            # sequencer.  This should be used with just one
1632*134e1779SJakub Wojciech Klama            # typedef (typedef s: _string_).
1633*134e1779SJakub Wojciech Klama            if token.value == _STRING_MAGIC:
1634*134e1779SJakub Wojciech Klama                names.append(_STRING_MAGIC) # XXX temporary
1635*134e1779SJakub Wojciech Klama                continue
1636*134e1779SJakub Wojciech Klama            if len(tokens) == 0 or tokens[0].ttype != 'type':
1637*134e1779SJakub Wojciech Klama                raise ValueError('parse error after {0}'.format(token))
1638*134e1779SJakub Wojciech Klama            type_or_size = tokens.popleft()
1639*134e1779SJakub Wojciech Klama            # Check for name[name2] where name2 is a word (not a
1640*134e1779SJakub Wojciech Klama            # number) that is in the names[] array.
1641*134e1779SJakub Wojciech Klama            if type_or_size.value in names:
1642*134e1779SJakub Wojciech Klama                # NB: this cannot have auxiliary info.
1643*134e1779SJakub Wojciech Klama                emit_bytes_repeat(token.value, type_or_size.value)
1644*134e1779SJakub Wojciech Klama                continue
1645*134e1779SJakub Wojciech Klama            if len(tokens) > 0 and tokens[0].ttype == 'aux':
1646*134e1779SJakub Wojciech Klama                aux = tokens.popleft()
1647*134e1779SJakub Wojciech Klama                if aux.value != 'auto':
1648*134e1779SJakub Wojciech Klama                    raise ValueError('{0}{1}: only know "auto", not '
1649*134e1779SJakub Wojciech Klama                                     '{2}'.format(token, type_or_size,
1650*134e1779SJakub Wojciech Klama                                                  aux.value))
1651*134e1779SJakub Wojciech Klama                emit_simple(token.value, type_or_size, aux.value)
1652*134e1779SJakub Wojciech Klama            else:
1653*134e1779SJakub Wojciech Klama                emit_simple(token.value, type_or_size)
1654*134e1779SJakub Wojciech Klama            continue
1655*134e1779SJakub Wojciech Klama        raise ValueError('"{0}" not valid here"'.format(token))
1656*134e1779SJakub Wojciech Klama
1657*134e1779SJakub Wojciech Klama    if cond is not None:
1658*134e1779SJakub Wojciech Klama        raise ValueError('unclosed "}"')
1659*134e1779SJakub Wojciech Klama
1660*134e1779SJakub Wojciech Klama    return names
1661*134e1779SJakub Wojciech Klama
1662*134e1779SJakub Wojciech Klamaclass _ProtoDefs(object):
1663*134e1779SJakub Wojciech Klama    def __init__(self):
1664*134e1779SJakub Wojciech Klama        # Scan our typedefs. This may execute '#define's as well.
1665*134e1779SJakub Wojciech Klama        self.typedefs = {}
1666*134e1779SJakub Wojciech Klama        self.defines = {}
1667*134e1779SJakub Wojciech Klama        typedef_re = re.compile(r'\s*typedef\s+(\w+)\s*:\s*(.*)')
1668*134e1779SJakub Wojciech Klama        self.parse_lines('SDesc', SDesc, typedef_re, self.handle_typedef)
1669*134e1779SJakub Wojciech Klama        self.parse_lines('QIDDesc', QIDDesc, typedef_re, self.handle_typedef)
1670*134e1779SJakub Wojciech Klama        self.parse_lines('STATDesc', STATDesc, typedef_re, self.handle_typedef)
1671*134e1779SJakub Wojciech Klama        self.parse_lines('WirestatDesc', WirestatDesc, typedef_re,
1672*134e1779SJakub Wojciech Klama                         self.handle_typedef)
1673*134e1779SJakub Wojciech Klama        self.parse_lines('DirentDesc', DirentDesc, typedef_re,
1674*134e1779SJakub Wojciech Klama                         self.handle_typedef)
1675*134e1779SJakub Wojciech Klama
1676*134e1779SJakub Wojciech Klama        # Scan protocol (the bulk of the work).  This, too, may
1677*134e1779SJakub Wojciech Klama        # execute '#define's.
1678*134e1779SJakub Wojciech Klama        self.protocol = {}
1679*134e1779SJakub Wojciech Klama        proto_re = re.compile(r'(\*?\w+)(\.\w+)?\s*(?:=\s*(\d+))?\s*:\s*(.*)')
1680*134e1779SJakub Wojciech Klama        self.prev_proto_value = None
1681*134e1779SJakub Wojciech Klama        self.parse_lines('ProtocolDesc', ProtocolDesc,
1682*134e1779SJakub Wojciech Klama                         proto_re, self.handle_proto_def)
1683*134e1779SJakub Wojciech Klama
1684*134e1779SJakub Wojciech Klama        self.setup_header()
1685*134e1779SJakub Wojciech Klama
1686*134e1779SJakub Wojciech Klama        # set these up for export()
1687*134e1779SJakub Wojciech Klama        self.plain = {}
1688*134e1779SJakub Wojciech Klama        self.dotu = {}
1689*134e1779SJakub Wojciech Klama        self.dotl = {}
1690*134e1779SJakub Wojciech Klama
1691*134e1779SJakub Wojciech Klama    def parse_lines(self, name, text, regexp, match_handler):
1692*134e1779SJakub Wojciech Klama        """
1693*134e1779SJakub Wojciech Klama        Parse a sequence of lines.  Match each line using the
1694*134e1779SJakub Wojciech Klama        given regexp, or (first) as a #define line.  Note that
1695*134e1779SJakub Wojciech Klama        indented lines are either #defines or are commentary!
1696*134e1779SJakub Wojciech Klama
1697*134e1779SJakub Wojciech Klama        If hnadling raises a ValueError, we complain and include
1698*134e1779SJakub Wojciech Klama        the appropriate line offset.  Then we sys.exit(1) (!).
1699*134e1779SJakub Wojciech Klama        """
1700*134e1779SJakub Wojciech Klama        define = re.compile(r'\s*#define\s+(\w+)\s+([^/]*)'
1701*134e1779SJakub Wojciech Klama                            r'(\s*/\*.*\*/)?\s*$')
1702*134e1779SJakub Wojciech Klama        for lineoff, line in enumerate(text.splitlines()):
1703*134e1779SJakub Wojciech Klama            try:
1704*134e1779SJakub Wojciech Klama                match = define.match(line)
1705*134e1779SJakub Wojciech Klama                if match:
1706*134e1779SJakub Wojciech Klama                    self.handle_define(*match.groups())
1707*134e1779SJakub Wojciech Klama                    continue
1708*134e1779SJakub Wojciech Klama                match = regexp.match(line)
1709*134e1779SJakub Wojciech Klama                if match:
1710*134e1779SJakub Wojciech Klama                    match_handler(*match.groups())
1711*134e1779SJakub Wojciech Klama                    continue
1712*134e1779SJakub Wojciech Klama                if len(line) and not line[0].isspace():
1713*134e1779SJakub Wojciech Klama                    raise ValueError('unhandled line: {0}'.format(line))
1714*134e1779SJakub Wojciech Klama            except ValueError as err:
1715*134e1779SJakub Wojciech Klama                print('Internal error while parsing {0}:\n'
1716*134e1779SJakub Wojciech Klama                      '    {1}\n'
1717*134e1779SJakub Wojciech Klama                      '(at line offset +{2}, discounting \\-newline)\n'
1718*134e1779SJakub Wojciech Klama                      'The original line in question reads:\n'
1719*134e1779SJakub Wojciech Klama                      '{3}'.format(name, err.args[0], lineoff, line),
1720*134e1779SJakub Wojciech Klama                      file=sys.stderr)
1721*134e1779SJakub Wojciech Klama                sys.exit(1)
1722*134e1779SJakub Wojciech Klama
1723*134e1779SJakub Wojciech Klama    def handle_define(self, name, value, comment):
1724*134e1779SJakub Wojciech Klama        """
1725*134e1779SJakub Wojciech Klama        Handle #define match.
1726*134e1779SJakub Wojciech Klama
1727*134e1779SJakub Wojciech Klama        The regexp has three fields, matching the name, value,
1728*134e1779SJakub Wojciech Klama        and possibly-empty comment; these are our arguments.
1729*134e1779SJakub Wojciech Klama        """
1730*134e1779SJakub Wojciech Klama        # Obnoxious: int(,0) requires new 0o syntax in py3k;
1731*134e1779SJakub Wojciech Klama        # work around by trying twice, once with base 0, then again
1732*134e1779SJakub Wojciech Klama        # with explicit base 8 if the first attempt fails.
1733*134e1779SJakub Wojciech Klama        try:
1734*134e1779SJakub Wojciech Klama            value = int(value, 0)
1735*134e1779SJakub Wojciech Klama        except ValueError:
1736*134e1779SJakub Wojciech Klama            value = int(value, 8)
1737*134e1779SJakub Wojciech Klama        if DEBUG:
1738*134e1779SJakub Wojciech Klama            print('define: defining {0} as {1:x}'.format(name, value),
1739*134e1779SJakub Wojciech Klama                  file=sys.stderr)
1740*134e1779SJakub Wojciech Klama        if name in self.defines:
1741*134e1779SJakub Wojciech Klama            raise ValueError('redefining {0}'.format(name))
1742*134e1779SJakub Wojciech Klama        self.defines[name] = (value, comment)
1743*134e1779SJakub Wojciech Klama
1744*134e1779SJakub Wojciech Klama    def handle_typedef(self, name, expr):
1745*134e1779SJakub Wojciech Klama        """
1746*134e1779SJakub Wojciech Klama        Handle typedef match.
1747*134e1779SJakub Wojciech Klama
1748*134e1779SJakub Wojciech Klama        The regexp has just two fields, the name and the expression
1749*134e1779SJakub Wojciech Klama        to parse (note that the expression must fit all on one line,
1750*134e1779SJakub Wojciech Klama        using backslach-newline if needed).
1751*134e1779SJakub Wojciech Klama
1752*134e1779SJakub Wojciech Klama        Typedefs may refer back to existing typedefs, so we pass
1753*134e1779SJakub Wojciech Klama        self.typedefs to _parse_expr().
1754*134e1779SJakub Wojciech Klama        """
1755*134e1779SJakub Wojciech Klama        seq = sequencer.Sequencer(name)
1756*134e1779SJakub Wojciech Klama        fields = _parse_expr(seq, expr, self.typedefs)
1757*134e1779SJakub Wojciech Klama        # Check for special string magic typedef.  (The name
1758*134e1779SJakub Wojciech Klama        # probably should be just 's' but we won't check that
1759*134e1779SJakub Wojciech Klama        # here.)
1760*134e1779SJakub Wojciech Klama        if len(fields) == 1 and fields[0] == _STRING_MAGIC:
1761*134e1779SJakub Wojciech Klama            cls = None
1762*134e1779SJakub Wojciech Klama        else:
1763*134e1779SJakub Wojciech Klama            cls = pfod.pfod(name, fields)
1764*134e1779SJakub Wojciech Klama        if DEBUG:
1765*134e1779SJakub Wojciech Klama            print('typedef: {0} = {1!r}; '.format(name, fields),
1766*134e1779SJakub Wojciech Klama                  end='', file=sys.stderr)
1767*134e1779SJakub Wojciech Klama            _debug_print_sequencer(seq)
1768*134e1779SJakub Wojciech Klama        if name in self.typedefs:
1769*134e1779SJakub Wojciech Klama            raise ValueError('redefining {0}'.format(name))
1770*134e1779SJakub Wojciech Klama        self.typedefs[name] = cls, seq
1771*134e1779SJakub Wojciech Klama
1772*134e1779SJakub Wojciech Klama    def handle_proto_def(self, name, proto_version, value, expr):
1773*134e1779SJakub Wojciech Klama        """
1774*134e1779SJakub Wojciech Klama        Handle protocol definition.
1775*134e1779SJakub Wojciech Klama
1776*134e1779SJakub Wojciech Klama        The regexp matched:
1777*134e1779SJakub Wojciech Klama        - The name of the protocol option such as Tversion,
1778*134e1779SJakub Wojciech Klama          Rversion, Rlerror, etc.
1779*134e1779SJakub Wojciech Klama        - The protocol version, if any (.u or .L).
1780*134e1779SJakub Wojciech Klama        - The value, if specified.  If no value is specified
1781*134e1779SJakub Wojciech Klama          we use "the next value".
1782*134e1779SJakub Wojciech Klama        - The expression to parse.
1783*134e1779SJakub Wojciech Klama
1784*134e1779SJakub Wojciech Klama        As with typedefs, the expression must fit all on one
1785*134e1779SJakub Wojciech Klama        line.
1786*134e1779SJakub Wojciech Klama        """
1787*134e1779SJakub Wojciech Klama        if value:
1788*134e1779SJakub Wojciech Klama            value = int(value)
1789*134e1779SJakub Wojciech Klama        elif self.prev_proto_value is not None:
1790*134e1779SJakub Wojciech Klama            value = self.prev_proto_value + 1
1791*134e1779SJakub Wojciech Klama        else:
1792*134e1779SJakub Wojciech Klama            raise ValueError('{0}: missing protocol value'.format(name))
1793*134e1779SJakub Wojciech Klama        if value < 0 or value > 255:
1794*134e1779SJakub Wojciech Klama            raise ValueError('{0}: protocol value {1} out of '
1795*134e1779SJakub Wojciech Klama                             'range'.format(name, value))
1796*134e1779SJakub Wojciech Klama        self.prev_proto_value = value
1797*134e1779SJakub Wojciech Klama
1798*134e1779SJakub Wojciech Klama        seq = sequencer.Sequencer(name)
1799*134e1779SJakub Wojciech Klama        fields = _parse_expr(seq, expr, self.typedefs)
1800*134e1779SJakub Wojciech Klama        cls = pfod.pfod(name, fields)
1801*134e1779SJakub Wojciech Klama        if DEBUG:
1802*134e1779SJakub Wojciech Klama            print('proto: {0} = {1}; '.format(name, value),
1803*134e1779SJakub Wojciech Klama                  end='', file=sys.stderr)
1804*134e1779SJakub Wojciech Klama            _debug_print_sequencer(seq)
1805*134e1779SJakub Wojciech Klama        if name in self.protocol:
1806*134e1779SJakub Wojciech Klama            raise ValueError('redefining {0}'.format(name))
1807*134e1779SJakub Wojciech Klama        self.protocol[name] = cls, value, proto_version, seq
1808*134e1779SJakub Wojciech Klama
1809*134e1779SJakub Wojciech Klama    def setup_header(self):
1810*134e1779SJakub Wojciech Klama        """
1811*134e1779SJakub Wojciech Klama        Handle header definition.
1812*134e1779SJakub Wojciech Klama
1813*134e1779SJakub Wojciech Klama        This is a bit gimmicky and uses some special cases,
1814*134e1779SJakub Wojciech Klama        because data is sized to dsize which is effectively
1815*134e1779SJakub Wojciech Klama        just size - 5.  We can't express this in our mini language,
1816*134e1779SJakub Wojciech Klama        so we just hard-code the sequencer and pfod.
1817*134e1779SJakub Wojciech Klama
1818*134e1779SJakub Wojciech Klama        In addition, the unpacker never gets the original packet's
1819*134e1779SJakub Wojciech Klama        size field, only the fcall and the data.
1820*134e1779SJakub Wojciech Klama        """
1821*134e1779SJakub Wojciech Klama        self.header_pfod = pfod.pfod('Header', 'size dsize fcall data')
1822*134e1779SJakub Wojciech Klama
1823*134e1779SJakub Wojciech Klama        seq = sequencer.Sequencer('Header-pack')
1824*134e1779SJakub Wojciech Klama        # size: 4 bytes
1825*134e1779SJakub Wojciech Klama        seq.append_encdec(None, sequencer.EncDecSimple('size', 4, None))
1826*134e1779SJakub Wojciech Klama        # fcall: 1 byte
1827*134e1779SJakub Wojciech Klama        seq.append_encdec(None, sequencer.EncDecSimple('fcall', 1, None))
1828*134e1779SJakub Wojciech Klama        # data: string of length dsize
1829*134e1779SJakub Wojciech Klama        seq.append_encdec(None, sequencer.EncDecA('dsize', 'data', None))
1830*134e1779SJakub Wojciech Klama        if DEBUG:
1831*134e1779SJakub Wojciech Klama            print('Header-pack:', file=sys.stderr)
1832*134e1779SJakub Wojciech Klama            _debug_print_sequencer(seq)
1833*134e1779SJakub Wojciech Klama        self.header_pack_seq = seq
1834*134e1779SJakub Wojciech Klama
1835*134e1779SJakub Wojciech Klama        seq = sequencer.Sequencer('Header-unpack')
1836*134e1779SJakub Wojciech Klama        seq.append_encdec(None, sequencer.EncDecSimple('fcall', 1, None))
1837*134e1779SJakub Wojciech Klama        seq.append_encdec(None, sequencer.EncDecA('dsize', 'data', None))
1838*134e1779SJakub Wojciech Klama        if DEBUG:
1839*134e1779SJakub Wojciech Klama            print('Header-unpack:', file=sys.stderr)
1840*134e1779SJakub Wojciech Klama            _debug_print_sequencer(seq)
1841*134e1779SJakub Wojciech Klama        self.header_unpack_seq = seq
1842*134e1779SJakub Wojciech Klama
1843*134e1779SJakub Wojciech Klama    def export(self, mod):
1844*134e1779SJakub Wojciech Klama        """
1845*134e1779SJakub Wojciech Klama        Dump results of internal parsing process
1846*134e1779SJakub Wojciech Klama        into our module namespace.
1847*134e1779SJakub Wojciech Klama
1848*134e1779SJakub Wojciech Klama        Note that we do not export the 's' typedef, which
1849*134e1779SJakub Wojciech Klama        did not define a data structure.
1850*134e1779SJakub Wojciech Klama
1851*134e1779SJakub Wojciech Klama        Check for name collisions while we're at it.
1852*134e1779SJakub Wojciech Klama        """
1853*134e1779SJakub Wojciech Klama        namespace = type('td', (object,), {})
1854*134e1779SJakub Wojciech Klama
1855*134e1779SJakub Wojciech Klama        # Export the typedefs (qid, stat).
1856*134e1779SJakub Wojciech Klama        setattr(mod, 'td', namespace)
1857*134e1779SJakub Wojciech Klama        for key in self.typedefs:
1858*134e1779SJakub Wojciech Klama            cls = self.typedefs[key][0]
1859*134e1779SJakub Wojciech Klama            if cls is None:
1860*134e1779SJakub Wojciech Klama                continue
1861*134e1779SJakub Wojciech Klama            setattr(namespace, key, cls)
1862*134e1779SJakub Wojciech Klama
1863*134e1779SJakub Wojciech Klama        # Export two sequencers for en/decoding stat fields
1864*134e1779SJakub Wojciech Klama        # (needed for reading directories and doing Twstat).
1865*134e1779SJakub Wojciech Klama        setattr(namespace, 'stat_seq', self.typedefs['stat'][1])
1866*134e1779SJakub Wojciech Klama        setattr(namespace, 'wirestat_seq', self.typedefs['wirestat'][1])
1867*134e1779SJakub Wojciech Klama
1868*134e1779SJakub Wojciech Klama        # Export the similar dirent decoder.
1869*134e1779SJakub Wojciech Klama        setattr(namespace, 'dirent_seq', self.typedefs['dirent'][1])
1870*134e1779SJakub Wojciech Klama
1871*134e1779SJakub Wojciech Klama        # Export the #define values
1872*134e1779SJakub Wojciech Klama        for key, val in self.defines.items():
1873*134e1779SJakub Wojciech Klama            if hasattr(namespace, key):
1874*134e1779SJakub Wojciech Klama                print('{0!r} is both a #define and a typedef'.format(key))
1875*134e1779SJakub Wojciech Klama                raise AssertionError('bad internal names')
1876*134e1779SJakub Wojciech Klama            setattr(namespace, key, val[0])
1877*134e1779SJakub Wojciech Klama
1878*134e1779SJakub Wojciech Klama        # Export Tattach, Rattach, Twrite, Rversion, etc values.
1879*134e1779SJakub Wojciech Klama        # Set up fcall_names[] table to map from value back to name.
1880*134e1779SJakub Wojciech Klama        # We also map fcall names to themselves, so given either a
1881*134e1779SJakub Wojciech Klama        # name or a byte code we can find out whether it's a valid
1882*134e1779SJakub Wojciech Klama        # fcall.
1883*134e1779SJakub Wojciech Klama        for key, val in self.protocol.items():
1884*134e1779SJakub Wojciech Klama            if hasattr(namespace, key):
1885*134e1779SJakub Wojciech Klama                prev_def = '#define' if key in self.defines else 'typedef'
1886*134e1779SJakub Wojciech Klama                print('{0!r} is both a {1} and a protocol '
1887*134e1779SJakub Wojciech Klama                      'value'.format(key, prev_def))
1888*134e1779SJakub Wojciech Klama                raise AssertionError('bad internal names')
1889*134e1779SJakub Wojciech Klama            setattr(namespace, key, val[1])
1890*134e1779SJakub Wojciech Klama            fcall_names[key] = key
1891*134e1779SJakub Wojciech Klama            fcall_names[val[1]] = key
1892*134e1779SJakub Wojciech Klama
1893*134e1779SJakub Wojciech Klama        # Hook up PFOD's for each protocol object -- for
1894*134e1779SJakub Wojciech Klama        # Tversion/Rversion, Twrite/Rwrite, Tlopen/Rlopen, etc.
1895*134e1779SJakub Wojciech Klama        # They go in the rrd name-space, and also in dictionaries
1896*134e1779SJakub Wojciech Klama        # per-protocol here, with the lookup pointing to a _PackInfo
1897*134e1779SJakub Wojciech Klama        # for the corresponding sequencer.
1898*134e1779SJakub Wojciech Klama        #
1899*134e1779SJakub Wojciech Klama        # Note that each protocol PFOD is optionally annotated with
1900*134e1779SJakub Wojciech Klama        # its specific version.  We know that .L > .u > plain; but
1901*134e1779SJakub Wojciech Klama        # all the "lesser" PFODs are available to all "greater"
1902*134e1779SJakub Wojciech Klama        # protocols at all times.
1903*134e1779SJakub Wojciech Klama        #
1904*134e1779SJakub Wojciech Klama        # (This is sort-of-wrong for Rerror vs Rlerror, but we
1905*134e1779SJakub Wojciech Klama        # don't bother to exclude Rerror from .L.)
1906*134e1779SJakub Wojciech Klama        #
1907*134e1779SJakub Wojciech Klama        # The PFODs themselves were already created, at parse time.
1908*134e1779SJakub Wojciech Klama        namespace = type('rrd', (object,), {})
1909*134e1779SJakub Wojciech Klama        setattr(mod, 'rrd', namespace)
1910*134e1779SJakub Wojciech Klama        for key, val in self.protocol.items():
1911*134e1779SJakub Wojciech Klama            cls = val[0]
1912*134e1779SJakub Wojciech Klama            proto_version = val[2]
1913*134e1779SJakub Wojciech Klama            seq = val[3]
1914*134e1779SJakub Wojciech Klama            packinfo = _PackInfo(seq)
1915*134e1779SJakub Wojciech Klama            if proto_version is None:
1916*134e1779SJakub Wojciech Klama                # all three protocols have it
1917*134e1779SJakub Wojciech Klama                self.plain[cls] = packinfo
1918*134e1779SJakub Wojciech Klama                self.dotu[cls] = packinfo
1919*134e1779SJakub Wojciech Klama                self.dotl[cls] = packinfo
1920*134e1779SJakub Wojciech Klama            elif proto_version == '.u':
1921*134e1779SJakub Wojciech Klama                # only .u and .L have it
1922*134e1779SJakub Wojciech Klama                self.dotu[cls] = packinfo
1923*134e1779SJakub Wojciech Klama                self.dotl[cls] = packinfo
1924*134e1779SJakub Wojciech Klama            elif proto_version == '.L':
1925*134e1779SJakub Wojciech Klama                # only .L has it
1926*134e1779SJakub Wojciech Klama                self.dotl[cls] = packinfo
1927*134e1779SJakub Wojciech Klama            else:
1928*134e1779SJakub Wojciech Klama                raise AssertionError('unknown protocol {1} for '
1929*134e1779SJakub Wojciech Klama                                     '{0}'.format(key, proto_version))
1930*134e1779SJakub Wojciech Klama            setattr(namespace, key, cls)
1931*134e1779SJakub Wojciech Klama
1932*134e1779SJakub Wojciech Klama_9p_data = _ProtoDefs()
1933*134e1779SJakub Wojciech Klama_9p_data.export(sys.modules[__name__])
1934*134e1779SJakub Wojciech Klama
1935*134e1779SJakub Wojciech Klama# Currently we look up by text-string, in lowercase.
1936*134e1779SJakub Wojciech Klama_9p_versions = {
1937*134e1779SJakub Wojciech Klama    '9p2000': _P9Proto({'version': '9P2000'},
1938*134e1779SJakub Wojciech Klama                       {'.u': False},
1939*134e1779SJakub Wojciech Klama                       _9p_data,
1940*134e1779SJakub Wojciech Klama                       _9p_data.plain,
1941*134e1779SJakub Wojciech Klama                       0),
1942*134e1779SJakub Wojciech Klama    '9p2000.u': _P9Proto({'version': '9P2000.u'},
1943*134e1779SJakub Wojciech Klama                         {'.u': True},
1944*134e1779SJakub Wojciech Klama                         _9p_data,
1945*134e1779SJakub Wojciech Klama                         _9p_data.dotu,
1946*134e1779SJakub Wojciech Klama                         1),
1947*134e1779SJakub Wojciech Klama    '9p2000.l': _P9Proto({'version': '9P2000.L'},
1948*134e1779SJakub Wojciech Klama                         {'.u': True},
1949*134e1779SJakub Wojciech Klama                         _9p_data,
1950*134e1779SJakub Wojciech Klama                         _9p_data.dotl,
1951*134e1779SJakub Wojciech Klama                         2),
1952*134e1779SJakub Wojciech Klama}
1953*134e1779SJakub Wojciech Klamadef p9_version(vers_string):
1954*134e1779SJakub Wojciech Klama    """
1955*134e1779SJakub Wojciech Klama    Return protocol implementation of given version.  Raises
1956*134e1779SJakub Wojciech Klama    KeyError if the version is invalid.  Note that the KeyError
1957*134e1779SJakub Wojciech Klama    will be on a string-ified, lower-cased version of the vers_string
1958*134e1779SJakub Wojciech Klama    argument, even if it comes in as a bytes instance in py3k.
1959*134e1779SJakub Wojciech Klama    """
1960*134e1779SJakub Wojciech Klama    if not isinstance(vers_string, str) and isinstance(vers_string, bytes):
1961*134e1779SJakub Wojciech Klama        vers_string = vers_string.decode('utf-8', 'surrogateescape')
1962*134e1779SJakub Wojciech Klama    return _9p_versions[vers_string.lower()]
1963*134e1779SJakub Wojciech Klama
1964*134e1779SJakub Wojciech Klamaplain = p9_version('9p2000')
1965*134e1779SJakub Wojciech Klamadotu = p9_version('9p2000.u')
1966*134e1779SJakub Wojciech Klamadotl = p9_version('9p2000.L')
1967*134e1779SJakub Wojciech Klama
1968*134e1779SJakub Wojciech Klamadef qid_type2name(qidtype):
1969*134e1779SJakub Wojciech Klama    """
1970*134e1779SJakub Wojciech Klama    Convert qid type field to printable string.
1971*134e1779SJakub Wojciech Klama
1972*134e1779SJakub Wojciech Klama    >>> qid_type2name(td.QTDIR)
1973*134e1779SJakub Wojciech Klama    'dir'
1974*134e1779SJakub Wojciech Klama    >>> qid_type2name(td.QTAPPEND)
1975*134e1779SJakub Wojciech Klama    'append-only'
1976*134e1779SJakub Wojciech Klama    >>> qid_type2name(0xff)
1977*134e1779SJakub Wojciech Klama    'invalid(0xff)'
1978*134e1779SJakub Wojciech Klama    """
1979*134e1779SJakub Wojciech Klama    try:
1980*134e1779SJakub Wojciech Klama        # Is it ever OK to have multiple bits set,
1981*134e1779SJakub Wojciech Klama        # e.g., both QTAPPEND and QTEXCL?
1982*134e1779SJakub Wojciech Klama        return {
1983*134e1779SJakub Wojciech Klama            td.QTDIR: 'dir',
1984*134e1779SJakub Wojciech Klama            td.QTAPPEND: 'append-only',
1985*134e1779SJakub Wojciech Klama            td.QTEXCL: 'exclusive',
1986*134e1779SJakub Wojciech Klama            td.QTMOUNT: 'mount',
1987*134e1779SJakub Wojciech Klama            td.QTAUTH: 'auth',
1988*134e1779SJakub Wojciech Klama            td.QTTMP: 'tmp',
1989*134e1779SJakub Wojciech Klama            td.QTSYMLINK: 'symlink',
1990*134e1779SJakub Wojciech Klama            td.QTFILE: 'file',
1991*134e1779SJakub Wojciech Klama        }[qidtype]
1992*134e1779SJakub Wojciech Klama    except KeyError:
1993*134e1779SJakub Wojciech Klama        pass
1994*134e1779SJakub Wojciech Klama    return 'invalid({0:#x})'.format(qidtype)
1995*134e1779SJakub Wojciech Klama
1996*134e1779SJakub Wojciech Klamaif __name__ == '__main__':
1997*134e1779SJakub Wojciech Klama    import doctest
1998*134e1779SJakub Wojciech Klama    doctest.testmod()
1999