~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

Linux Cross Reference
cvs/emstar/fusd/python/fusd.py


  1 #! /usr/bin/python
  2 
  3 #    Copyright (C) 2003  Brian Warner  <warner-fusd@lothar.com>
  4 #
  5 #    This program is free software, and can be distributed under the same
  6 #    terms as the rest of FUSD (the BSD 3-clause license). See the file
  7 #    ../LICENSE for details.
  8 
  9 import _fusd
 10 from _fusd import NOTIFY_INPUT, NOTIFY_OUTPUT, NOTIFY_EXCEPT
 11 import errno
 12 
 13 # fusd.run() never returns. To integrate with other event loops, use
 14 # Device.handle as a fileno and call Device.dispatch() when that fileno
 15 # becomes readable.
 16 
 17 run = _fusd.run
 18 
 19     
 20 class OpenFile:
 21     """OpenFile: one instance per open() of a device node
 22 
 23     Each time a process does an open() of your device node, a new instance
 24     of this class (or a subclass) will be created. There will be a
 25     one-to-one correspondence between file pointers and OpenFile instances.
 26 
 27     read()/write()/ioctl() system calls on that file pointer will result in
 28     do_read/do_write/do_ioctl method invocations on this object. Each call
 29     gets a Request object, from which the parameters of the system call can
 30     be retrieved. Data to be returned to the caller is given to methods of
 31     the request object. request.finish(retval) must eventually be called,
 32     either in the do_ method or later (say, for blocking reads).
 33     """
 34 
 35     poll_state = NOTIFY_OUTPUT
 36     poll_req = None
 37 
 38     def __init__(self, device):
 39         self.device = device
 40         self.flags = None # should probably be updated by req.flags
 41 
 42     def do_read(self, req):
 43         """do_read(self, request)
 44 
 45         This will be called each time the user does a read() of the device.
 46 
 47         'request' is a ReadRequest object with the following useful
 48         attributes:
 49 
 50         request.length (ro): the 'size' parameter passed to read()
 51         request.flags (rw): the flags with which the file was opened
 52         request.pid (ro): the PID of the process making the read() call
 53         request.uid (ro): the UID of the user owning the process doing read()
 54         request.gid (ro): the GID of the user owning the process
 55         request.offset (rw): the offset at which the read is being done
 56 
 57         'request' has the following methods:
 58 
 59         request.setdata(offset, data): update the return data buffer,
 60          starting at 'offset', by copying 'data' into the buffer. Attempts
 61          to overwrite the either end of the buffer (negative offsets,
 62          oversized 'data' arguments) will be caught and an IndexError
 63          exception raised.
 64         
 65         request.finish(retval): complete the request, returning 'retval' to
 66          the user's read() system call.
 67          
 68         request.destroy(): free the ReadRequest object. This should only be
 69          done if close() is called while a read() request is still
 70          outstanding.
 71 
 72 
 73         do_read() should usually do the following:
 74          return data by doing req.setdata(offset, data)
 75          update the offset by doing req.offset += len(data)
 76          allow the read() call to return by doing req.finish(len(data))
 77 
 78         It may defer these until later (to implement blocking reads), but
 79         eventually the request must be completed by calling req.finish. It
 80         is an error to let an uncompleted request fall out of scope.
 81 
 82         To return an error to the user doing read(), provide a negative
 83         error number like req.finish(-errno.EIO). To indicate EOF, provide
 84         zero, like req.finish(0).
 85 
 86         The default implementation of do_read() will use read() to get a
 87         chunk of data. To implement a blocking read, or to return an error
 88         other than EOF, you will need to override do_read().
 89         
 90         """
 91         
 92         data = self.read(req, req.length, req.offset)
 93         assert (len(data) <= req.length)
 94         req.offset += len(data)
 95         rc = len(data)
 96         req.setdata(0, data)
 97         req.finish(rc)
 98 
 99     def read(self, req, length, offset):
100         """read(self, req, length, offset)
101 
102         The default implementation of do_read() will call this each time the
103         user does a read() of the device. It is expected to return some
104         amount of data, no more than 'length' bytes, which start at 'offset'
105         of the imaginary data stream the device pretends to represent. The
106         user wants 'length', but the device can return fewer than that. The
107         offset will be incremented by the actual number of bytes returned.
108         Returning 0 indicates End Of File and will usually cause the user
109         program to close the device.
110         """
111         raise NotImplementedError, "your subclass must implement this method"
112 
113 
114     def do_write(self, req):
115         """do_write(self, request)
116 
117         This will be called each time the user does a write() of the device.
118 
119         'request' is a WriteRequest object with the following useful
120         attributes:
121 
122         request.length (ro): the 'size' parameter passed to write()
123         request.flags (rw): the flags with which the file was opened
124         request.pid (ro): the PID of the process making the write() call
125         request.uid (ro): the UID of the user owning the process doing write()
126         request.gid (ro): the GID of the user owning the process
127         request.offset (rw): the offset at which the write is being done
128 
129         'request' has the following methods:
130 
131         request.getdata(offset, length): copy data from the write buffer,
132          'length' bytes starting at 'offset'. Attempts to read past the
133          either end of the buffer (negative offsets, oversized 'length'
134          arguments) will be caught and an IndexError exception raised.
135         
136         request.finish(retval): complete the request, returning 'retval' to
137          the user's write() system call.
138          
139         request.destroy(): free the WriteRequest object. This should only be
140          done if close() is called while a write() request is still
141          outstanding.
142 
143 
144         do_write() should usually do the following:
145          decide how many bytes can be handled, say 'handled'
146          get data from the request by doing req.setdata(0, handled)
147          do something with that data
148          update the offset by doing req.offset += handled
149          allow the read() call to return by doing req.finish(handled)
150 
151         It may defer these until later (to implement blocking writes), but
152         eventually the request must be completed by calling req.finish. It
153         is an error to let an uncompleted request fall out of scope.
154 
155         To return an error to the user doing write(), provide a negative
156         error number like req.finish(-errno.EIO). To indicate EOF, provide
157         zero, like req.finish(0). (?? valid for writes?)
158 
159         The default implementation of do_write() will call self.write(),
160         which will simply receive everything the user tried to write. To
161         implement a blocking write, or to implement partial writes, you will
162         need to override do_read().
163         
164         """
165         rc = self.write(req, req.offset, req.getdata())
166         req.offset += rc
167         req.finish(rc)
168 
169     def write(self, req, offset, data):
170         raise NotImplementedError, "your subclass must implement this method"
171 
172 
173     def do_ioctl(self, req):
174         """do_ioctl(self, request)
175 
176         This will be called each time the user does a ioctl() on the device.
177 
178         'request' is an IoctlRequest object with the following useful
179         attributes:
180 
181         request.length (ro): ???
182         request.flags (rw): the flags with which the file was opened
183         request.pid (ro): the PID of the process making the read() call
184         request.uid (ro): the UID of the user owning the process doing read()
185         request.gid (ro): the GID of the user owning the process
186         request.offset (rw): the file's current offset value
187         request.cmd (ro): the ioctl number, second argument to ioctl() call.
188          This is system-dependent, but is usually a bit-field composed of
189          a direction, a type, a number, and a size.
190         request.direction (ro): _IOC_DIR, indicates in, in+out, out, neither
191         request.dir_string (ro): 'none', 'write', 'read', 'readwrite'
192         request.type (ro): _IOC_TYPE, a one-byte subsystem category
193         request.nr (ro): _IOC_NR, a one-byte method number
194         request.size (ro): _IOC_SIZE, indicates size of the argument which
195          is copied in or out
196 
197         'request' has the following methods:
198 
199         request.getdata(offset, length): copy data from the argument buffer
200         request.setdata(offset, data): copy data into the argument buffer
201          these behave just as in read/write
202         
203         request.finish(retval): complete the request, returning 'retval' to
204          the user's ioctl() system call.
205          
206         request.destroy(): free the IoctlRequest object. This should only be
207          done if close() is called while a ioctl() request is still
208          outstanding.
209 
210         do_ioctl() should probably look at request.dir_string, do
211         request.getdata() if it is 'write' or 'readwrite', then do something
212         with the resulting data. If .dir_string is 'read', it should return
213         request.size bytes of data by doing request.setdata(data) . When
214         done, it should do request.finish(0).
215 
216         If your device does not implement any ioctls, returning an error
217         code with request.finish(-errno.ENOSYS) is probably appropriate.
218 
219         The 'struct' module will probably be useful, as most
220         ioctls pass a C struct as their argument.
221         
222         """
223 
224         request.finish(-errno.ENOSYS)
225 
226 
227     # these methods control select()/poll() calls performed on the device.
228     # The initial state is controlled by self.poll_state, and NOTIFY_OUTPUT
229     # means the device is writable but not readable. To make a device that
230     # is always readable and writable (as far as select() is concerned), set
231     # poll_state to NOTIFY_INPUT | NOTIFY_OUTPUT. To change this state, call
232     # startReading/etc. The start/stop calls will wake up any clients that
233     # are waiting upon the status to change.
234     
235     def startReading(self):
236         self.poll_state |= NOTIFY_INPUT
237         if self.poll_req:
238             # trigger pending request
239             self.poll_req.finish(self.poll_state)
240             self.poll_req = None
241     def stopReading(self):
242         self.poll_state &= ~NOTIFY_INPUT
243 
244     def startWriting(self):
245         self.poll_state |= NOTIFY_OUTPUT
246         if self.poll_req:
247             self.poll_req.finish(self.poll_state)
248             self.poll_req = None
249     def stopWriting(self):
250         self.poll_state &= ~NOTIFY_OUTPUT
251 
252     def startException(self):
253         self.poll_state |= NOTIFY_EXCEPT
254         if self.poll_req:
255             self.poll_req.finish(self.poll_state)
256             self.poll_req = None
257     def stopException(self):
258         self.poll_state &= ~NOTIFY_EXCEPT
259 
260     def do_poll_diff(self, req, cached_state):
261         if self.poll_state == cached_state:
262             if self.poll_req:
263                 self.poll_req.destroy()
264             self.poll_req = req
265         else:
266             req.finish(self.poll_state)
267 
268 
269 class Device:
270     openFileClass = OpenFile
271     def __init__(self, name, mode):
272         # our Device pointer is stored as device_info, so it will be
273         # provided with all operations. It will also be searched for do_FOO
274         # methods.
275         self.handle = _fusd.register(name, mode, self)
276         # self.handle is a fileno
277 
278     def unregister(self):
279         rc = _fusd.unregister(self.handle, self)
280         return rc
281 
282     def dispatch(self):
283         """dispatch() will handle all pending requests for the given device.
284         self.handle is a fileno, which can be used in select() and poll(),
285         or handed to other event loops.
286         """
287         _fusd.dispatch(self.handle)
288 
289     def do_open(self, flags, pid, uid, gid):
290         fp = self.openFileClass(self)
291         # fp is stored as private_info, and will be provided with each
292         # operation involving this open file
293         fp.flags = flags
294         rc = self.open(fp, pid, uid, gid)
295         return (rc, fp.flags, fp)
296 
297     def open(self, fp, pid, uid, gid):
298         """open(): called when the device node is opened
299 
300         open() gets to reject the opening attempt (perhaps based upon user
301         id number) by returning non-zero. It can also add attributes to the
302         new file pointer by modifying 'fp' before it returns.
303         """
304 
305         print "open(flags=0x%x, pid=%d, uid/gid=%d/%d)" % (fp.flags,
306                                                            pid, uid, gid)
307         return 0 # success
308 
309     def do_close(self, fp, flags, pid, uid, gid):
310         fp.flags = flags
311         rc = self.close(fp, pid, uid, gid)
312         return (rc,)
313         
314     def close(self, fp, pid, uid, gid):
315         """close(): called when the open file pointer is finally closed
316 
317         close() is a good place to remove the OpenFile from a list of
318         currently open files.
319         """
320 
321         print "close(flags=0x%x, pid=%d, uid/gid=%d/%d)" % (fp.flags,
322                                                             pid, uid, gid)
323         return 0
324 
325 # useful examples
326 
327 class SampleDevice(Device):
328     class SampleFile(OpenFile):
329         def read(self, req, length, offset):
330             print "read[%d,%d]" % (offset, length)
331             if offset == 0:
332                 return "hello world\n"
333             else:
334                 return ""
335         def write(self, req, offset, data):
336             print "write[%d]" % offset, data
337             return len(data)
338             if req.dir_string in ("write", "readwrite"):
339                 data = req.getdata()
340             else:
341                 data = ""
342 
343         def do_ioctl(self, req):
344             if req.dir_string in ("write", "readwrite"):
345                 data = req.getdata()
346             else:
347                 data = ""
348             print "ioctl(%x: %s/%x/%x/%d): [%d]%s" % (req.cmd,
349                                                       req.dir_string,
350                                                       req.type, req.nr,
351                                                       req.size,
352                                                       len(data), data)
353             if req.dir_string in ("read", "readwrite"):
354                 req.setdata("x" * req.size)
355             req.finish(0)
356     openFileClass = SampleFile
357 
358 # other devices: to use, make a Device with .openFileClass = NullFile, etc
359 class NullFile(OpenFile):
360     """/dev/null equivalent"""
361     poll_state = NOTIFY_INPUT | NOTIFY_OUTPUT
362     def read(self, req, length, offset):
363         return ""
364     def write(self, req, offset, data):
365         return len(data)
366     def do_ioctl(self, req):
367         req.finish(-errno.ENOSYS)
368 
369 class ZeroFile(NullFile):
370     """/dev/zero equivalent"""
371     def read(self, req, length, offset):
372         return "\x00" * length
373 
374 class FullFile(OpenFile):
375     """/dev/full equivalent"""
376     poll_state = NOTIFY_OUTPUT
377     def read(self, req, length, offset):
378         while 1:
379             pass # ??
380     def write(self, req, offset, data):
381         return 0
382     def do_ioctl(self, req):
383         req.finish(-errno.ENOSYS)
384 
385 # OutFile/InFile act like a pty pair: once set up, writing to one will cause
386 # data to be available for reading from the other. They also serve as
387 # examples of the startReading/etc select/poll handling code.
388 
389 class OutFile(OpenFile):
390     readQueue = ""
391     readReq = None
392     def addToQueue(self, data):
393         self.readQueue += data
394         self.startReading()
395     def do_read(self, req):
396         assert(not self.readReq)
397         if self.readQueue:
398             self.read(req)
399         else:
400             self.readReq = req
401     def read(self, req):
402         l = min(req.length, len(self.readQueue))
403         req.setdata(0, self.readQueue[:l])
404         self.readQueue = self.readQueue[l:]
405         if not self.readQueue:
406             self.stopReading()
407         req.finish(l)
408 
409     def startReading(self):
410         if self.readReq:
411             self.read(self.readReq)
412             self.readReq = None
413         OpenFile.startReading(self)
414 
415 class OutDevice(Device):
416     openFileClass = OutFile
417     target = None
418     def open(self, fp, pid, uid, gid):
419         print "opened", fp
420         self.target = fp
421         return 0
422 
423 class InFile(OpenFile):
424     def write(self, req, offset, data):
425         if self.device.partner:
426             print "partner:", self.device.partner
427             print "target:", self.device.partner.target
428             self.device.partner.target.addToQueue(data)
429         return len(data)
430 
431 def setup_pty(indevice, outdevice):
432     d1 = OutDevice(outdevice, 0666)
433     d2 = Device(indevice, 0666)
434     d2.openFileClass = InFile
435     d2.partner = d1
436     run()

~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

This page was automatically generated by the LXR engine.
Visit the LXR main site for more information.