(file) Return to fusd.py CVS log (file) Jump to this file's LXR Page (dir) Up to [CENS] / emstar / fusd / python

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