|
|
Jump to this file's LXR Page |
|
|
File: [CENS] / emstar / fusd / python / fusd.py
(download)
/
(as text)
Revision: 1.1, Thu Jun 19 19:09:09 2003 UTC (6 years, 5 months ago) by jelson Branch: MAIN CVS Tags: scale_radio_channel, rdd_alpha_version_1, pregeonet, nims-lab-Sep07-2004, nims-jr-Sep05-04, mote, lessgps_release, kiss_release, fusd_with_no_daemon, fusd-1_10, copyright-07-11-03, bp_scale_radio_channel, audio_server, acoustic-05-18-06, ROUTING_EXPERIMENTAL, PRE_TOSNIC_FIX, PRE_NOMEGA_MOTENIC, PRE_MOTENIC_CLEANUP, PRE_CEILING_FIX, PRE_64BIT, MOTENIC_PRE_BUGFIX_20050415, LESSGPS_1_00, LAURA_CALIBRATION_EXPERIMENTS, KISS_1_0, HOSTMOTE_V_6_EXPERIMENTAL, HOSTMOTE_PROTOCOL_VERSION_7, HOSTMOTE_PROTOCOL_VERSION_6_WITH_HOSTMOAP, HOSTMOTE_PROTOCOL_VERSION_5_WITH_HOSTMOAP, HOSTMOTE_PROTOCOL_VERSION_5, HOSTMOTE_PROTOCOL_VERSION_4, HOSTMOTE_PROTOCOL_VERSION_3, HEAD, ESS_RELEASE_3_5, ESS_RELEASE_3_4, ESS_RELEASE_3_3, ESS_RELEASE_3_2, ESS_RELEASE_3_1, ESS_RELEASE_3_0, ESS_RELEASE_2_0, ESS_CONNECTIVITY, ESS_CENTROUTE_TESTING, ESS2-CMS-V1_5_pretest, ESS2-CMS-V1_4cMergeSympathy_2, ESS2-CMS-V1_4c, ESS2-CMS-V1_4b, ESS2-CMS-V1_4a, ESS2-CMS-V1_3, ESS2-CMS-V1_2, ESS2-CMS-V1_1, ESS2-CMS-V1_0, EMSTAR_RELEASE_2_5, EMSTAR_RELEASE_2_1_BRANCH, EMSTAR_RELEASE_2_1, EMSTAR_RELEASE_2_0_beta1, EMSTAR_RELEASE_2_0, EMSTAR_RELEASE_1_3_2, EMSTAR_RELEASE_1_3_1, EMSTAR_RELEASE_1_3, EMSTAR_RELEASE_1_2, EMSTAR_RELEASE_1_1, EMSTAR_RELEASE_1_0, EMSTAR_PRE_HTML, CYCLOPS_RELEASE_CANDIDATE_2_0, CYCLOPS_PRERELEASE_STABLE, CENTROUTE_EMSTAR_SOCKETS, BG_1_0, BANGLADESH_ARSENIC_1_2, BANGLADESH_ARSENIC_1_1, AMARSS_JR_DEPLOYMENT_6_05_07 Added first version of FUSD Python bindings from Brian Warner |
#! /usr/bin/python
# Copyright (C) 2003 Brian Warner <warner-fusd@lothar.com>
#
# This program is free software, and can be distributed under the same
# terms as the rest of FUSD (the BSD 3-clause license). See the file
# ../LICENSE for details.
import _fusd
from _fusd import NOTIFY_INPUT, NOTIFY_OUTPUT, NOTIFY_EXCEPT
import errno
# fusd.run() never returns. To integrate with other event loops, use
# Device.handle as a fileno and call Device.dispatch() when that fileno
# becomes readable.
run = _fusd.run
class OpenFile:
"""OpenFile: one instance per open() of a device node
Each time a process does an open() of your device node, a new instance
of this class (or a subclass) will be created. There will be a
one-to-one correspondence between file pointers and OpenFile instances.
read()/write()/ioctl() system calls on that file pointer will result in
do_read/do_write/do_ioctl method invocations on this object. Each call
gets a Request object, from which the parameters of the system call can
be retrieved. Data to be returned to the caller is given to methods of
the request object. request.finish(retval) must eventually be called,
either in the do_ method or later (say, for blocking reads).
"""
poll_state = NOTIFY_OUTPUT
poll_req = None
def __init__(self, device):
self.device = device
self.flags = None # should probably be updated by req.flags
def do_read(self, req):
"""do_read(self, request)
This will be called each time the user does a read() of the device.
'request' is a ReadRequest object with the following useful
attributes:
request.length (ro): the 'size' parameter passed to read()
request.flags (rw): the flags with which the file was opened
request.pid (ro): the PID of the process making the read() call
request.uid (ro): the UID of the user owning the process doing read()
request.gid (ro): the GID of the user owning the process
request.offset (rw): the offset at which the read is being done
'request' has the following methods:
request.setdata(offset, data): update the return data buffer,
starting at 'offset', by copying 'data' into the buffer. Attempts
to overwrite the either end of the buffer (negative offsets,
oversized 'data' arguments) will be caught and an IndexError
exception raised.
request.finish(retval): complete the request, returning 'retval' to
the user's read() system call.
request.destroy(): free the ReadRequest object. This should only be
done if close() is called while a read() request is still
outstanding.
do_read() should usually do the following:
return data by doing req.setdata(offset, data)
update the offset by doing req.offset += len(data)
allow the read() call to return by doing req.finish(len(data))
It may defer these until later (to implement blocking reads), but
eventually the request must be completed by calling req.finish. It
is an error to let an uncompleted request fall out of scope.
To return an error to the user doing read(), provide a negative
error number like req.finish(-errno.EIO). To indicate EOF, provide
zero, like req.finish(0).
The default implementation of do_read() will use read() to get a
chunk of data. To implement a blocking read, or to return an error
other than EOF, you will need to override do_read().
"""
data = self.read(req, req.length, req.offset)
assert (len(data) <= req.length)
req.offset += len(data)
rc = len(data)
req.setdata(0, data)
req.finish(rc)
def read(self, req, length, offset):
"""read(self, req, length, offset)
The default implementation of do_read() will call this each time the
user does a read() of the device. It is expected to return some
amount of data, no more than 'length' bytes, which start at 'offset'
of the imaginary data stream the device pretends to represent. The
user wants 'length', but the device can return fewer than that. The
offset will be incremented by the actual number of bytes returned.
Returning 0 indicates End Of File and will usually cause the user
program to close the device.
"""
raise NotImplementedError, "your subclass must implement this method"
def do_write(self, req):
"""do_write(self, request)
This will be called each time the user does a write() of the device.
'request' is a WriteRequest object with the following useful
attributes:
request.length (ro): the 'size' parameter passed to write()
request.flags (rw): the flags with which the file was opened
request.pid (ro): the PID of the process making the write() call
request.uid (ro): the UID of the user owning the process doing write()
request.gid (ro): the GID of the user owning the process
request.offset (rw): the offset at which the write is being done
'request' has the following methods:
request.getdata(offset, length): copy data from the write buffer,
'length' bytes starting at 'offset'. Attempts to read past the
either end of the buffer (negative offsets, oversized 'length'
arguments) will be caught and an IndexError exception raised.
request.finish(retval): complete the request, returning 'retval' to
the user's write() system call.
request.destroy(): free the WriteRequest object. This should only be
done if close() is called while a write() request is still
outstanding.
do_write() should usually do the following:
decide how many bytes can be handled, say 'handled'
get data from the request by doing req.setdata(0, handled)
do something with that data
update the offset by doing req.offset += handled
allow the read() call to return by doing req.finish(handled)
It may defer these until later (to implement blocking writes), but
eventually the request must be completed by calling req.finish. It
is an error to let an uncompleted request fall out of scope.
To return an error to the user doing write(), provide a negative
error number like req.finish(-errno.EIO). To indicate EOF, provide
zero, like req.finish(0). (?? valid for writes?)
The default implementation of do_write() will call self.write(),
which will simply receive everything the user tried to write. To
implement a blocking write, or to implement partial writes, you will
need to override do_read().
"""
rc = self.write(req, req.offset, req.getdata())
req.offset += rc
req.finish(rc)
def write(self, req, offset, data):
raise NotImplementedError, "your subclass must implement this method"
def do_ioctl(self, req):
"""do_ioctl(self, request)
This will be called each time the user does a ioctl() on the device.
'request' is an IoctlRequest object with the following useful
attributes:
request.length (ro): ???
request.flags (rw): the flags with which the file was opened
request.pid (ro): the PID of the process making the read() call
request.uid (ro): the UID of the user owning the process doing read()
request.gid (ro): the GID of the user owning the process
request.offset (rw): the file's current offset value
request.cmd (ro): the ioctl number, second argument to ioctl() call.
This is system-dependent, but is usually a bit-field composed of
a direction, a type, a number, and a size.
request.direction (ro): _IOC_DIR, indicates in, in+out, out, neither
request.dir_string (ro): 'none', 'write', 'read', 'readwrite'
request.type (ro): _IOC_TYPE, a one-byte subsystem category
request.nr (ro): _IOC_NR, a one-byte method number
request.size (ro): _IOC_SIZE, indicates size of the argument which
is copied in or out
'request' has the following methods:
request.getdata(offset, length): copy data from the argument buffer
request.setdata(offset, data): copy data into the argument buffer
these behave just as in read/write
request.finish(retval): complete the request, returning 'retval' to
the user's ioctl() system call.
request.destroy(): free the IoctlRequest object. This should only be
done if close() is called while a ioctl() request is still
outstanding.
do_ioctl() should probably look at request.dir_string, do
request.getdata() if it is 'write' or 'readwrite', then do something
with the resulting data. If .dir_string is 'read', it should return
request.size bytes of data by doing request.setdata(data) . When
done, it should do request.finish(0).
If your device does not implement any ioctls, returning an error
code with request.finish(-errno.ENOSYS) is probably appropriate.
The 'struct' module will probably be useful, as most
ioctls pass a C struct as their argument.
"""
request.finish(-errno.ENOSYS)
# these methods control select()/poll() calls performed on the device.
# The initial state is controlled by self.poll_state, and NOTIFY_OUTPUT
# means the device is writable but not readable. To make a device that
# is always readable and writable (as far as select() is concerned), set
# poll_state to NOTIFY_INPUT | NOTIFY_OUTPUT. To change this state, call
# startReading/etc. The start/stop calls will wake up any clients that
# are waiting upon the status to change.
def startReading(self):
self.poll_state |= NOTIFY_INPUT
if self.poll_req:
# trigger pending request
self.poll_req.finish(self.poll_state)
self.poll_req = None
def stopReading(self):
self.poll_state &= ~NOTIFY_INPUT
def startWriting(self):
self.poll_state |= NOTIFY_OUTPUT
if self.poll_req:
self.poll_req.finish(self.poll_state)
self.poll_req = None
def stopWriting(self):
self.poll_state &= ~NOTIFY_OUTPUT
def startException(self):
self.poll_state |= NOTIFY_EXCEPT
if self.poll_req:
self.poll_req.finish(self.poll_state)
self.poll_req = None
def stopException(self):
self.poll_state &= ~NOTIFY_EXCEPT
def do_poll_diff(self, req, cached_state):
if self.poll_state == cached_state:
if self.poll_req:
self.poll_req.destroy()
self.poll_req = req
else:
req.finish(self.poll_state)
class Device:
openFileClass = OpenFile
def __init__(self, name, mode):
# our Device pointer is stored as device_info, so it will be
# provided with all operations. It will also be searched for do_FOO
# methods.
self.handle = _fusd.register(name, mode, self)
# self.handle is a fileno
def unregister(self):
rc = _fusd.unregister(self.handle, self)
return rc
def dispatch(self):
"""dispatch() will handle all pending requests for the given device.
self.handle is a fileno, which can be used in select() and poll(),
or handed to other event loops.
"""
_fusd.dispatch(self.handle)
def do_open(self, flags, pid, uid, gid):
fp = self.openFileClass(self)
# fp is stored as private_info, and will be provided with each
# operation involving this open file
fp.flags = flags
rc = self.open(fp, pid, uid, gid)
return (rc, fp.flags, fp)
def open(self, fp, pid, uid, gid):
"""open(): called when the device node is opened
open() gets to reject the opening attempt (perhaps based upon user
id number) by returning non-zero. It can also add attributes to the
new file pointer by modifying 'fp' before it returns.
"""
print "open(flags=0x%x, pid=%d, uid/gid=%d/%d)" % (fp.flags,
pid, uid, gid)
return 0 # success
def do_close(self, fp, flags, pid, uid, gid):
fp.flags = flags
rc = self.close(fp, pid, uid, gid)
return (rc,)
def close(self, fp, pid, uid, gid):
"""close(): called when the open file pointer is finally closed
close() is a good place to remove the OpenFile from a list of
currently open files.
"""
print "close(flags=0x%x, pid=%d, uid/gid=%d/%d)" % (fp.flags,
pid, uid, gid)
return 0
# useful examples
class SampleDevice(Device):
class SampleFile(OpenFile):
def read(self, req, length, offset):
print "read[%d,%d]" % (offset, length)
if offset == 0:
return "hello world\n"
else:
return ""
def write(self, req, offset, data):
print "write[%d]" % offset, data
return len(data)
if req.dir_string in ("write", "readwrite"):
data = req.getdata()
else:
data = ""
def do_ioctl(self, req):
if req.dir_string in ("write", "readwrite"):
data = req.getdata()
else:
data = ""
print "ioctl(%x: %s/%x/%x/%d): [%d]%s" % (req.cmd,
req.dir_string,
req.type, req.nr,
req.size,
len(data), data)
if req.dir_string in ("read", "readwrite"):
req.setdata("x" * req.size)
req.finish(0)
openFileClass = SampleFile
# other devices: to use, make a Device with .openFileClass = NullFile, etc
class NullFile(OpenFile):
"""/dev/null equivalent"""
poll_state = NOTIFY_INPUT | NOTIFY_OUTPUT
def read(self, req, length, offset):
return ""
def write(self, req, offset, data):
return len(data)
def do_ioctl(self, req):
req.finish(-errno.ENOSYS)
class ZeroFile(NullFile):
"""/dev/zero equivalent"""
def read(self, req, length, offset):
return "\x00" * length
class FullFile(OpenFile):
"""/dev/full equivalent"""
poll_state = NOTIFY_OUTPUT
def read(self, req, length, offset):
while 1:
pass # ??
def write(self, req, offset, data):
return 0
def do_ioctl(self, req):
req.finish(-errno.ENOSYS)
# OutFile/InFile act like a pty pair: once set up, writing to one will cause
# data to be available for reading from the other. They also serve as
# examples of the startReading/etc select/poll handling code.
class OutFile(OpenFile):
readQueue = ""
readReq = None
def addToQueue(self, data):
self.readQueue += data
self.startReading()
def do_read(self, req):
assert(not self.readReq)
if self.readQueue:
self.read(req)
else:
self.readReq = req
def read(self, req):
l = min(req.length, len(self.readQueue))
req.setdata(0, self.readQueue[:l])
self.readQueue = self.readQueue[l:]
if not self.readQueue:
self.stopReading()
req.finish(l)
def startReading(self):
if self.readReq:
self.read(self.readReq)
self.readReq = None
OpenFile.startReading(self)
class OutDevice(Device):
openFileClass = OutFile
target = None
def open(self, fp, pid, uid, gid):
print "opened", fp
self.target = fp
return 0
class InFile(OpenFile):
def write(self, req, offset, data):
if self.device.partner:
print "partner:", self.device.partner
print "target:", self.device.partner.target
self.device.partner.target.addToQueue(data)
return len(data)
def setup_pty(indevice, outdevice):
d1 = OutDevice(outdevice, 0666)
d2 = Device(indevice, 0666)
d2.openFileClass = InFile
d2.partner = d1
run()
| CENS CVS Mailing List |
Powered by ViewCVS 0.9.2 |