Tuesday, May 24, 2011

Converting Errno to an Exception

The way Linux and associated libs do error handling is very passive in that if a function call fails, or otherwise does not succeed, it returns a non zero integer. Often it returns -1 and sets the errno integer. It is than the programs responsibility to check the value of errno to figure out what failed and how to recover. This is my understanding; not being a Linux developer.

Python on the other hand is more active in that if something fails or faults, it raises an exception that typically must be handled or the program will exit, dumping a traceback to stderr.

The TeeOS solution is to turn each errno into an exception class of its own:

ErrnoExcepts = {}

# errno 1
class EPERM(Exception):
    def __init__(self):
        self.desc = 'Operation not permitted'
        self.errno = 1
    def __str__(self):
        return repr(self.desc)
ErrnoExcepts[1] = EPERM

# errno 2
class ENOENT(Exception):
    def __init__(self):
        self.desc = 'No such file or directory'
        self.errno = 2
    def __str__(self):
        return repr(self.desc)
ErrnoExcepts[2] = ENOENT
def RaiseErrnoException(errno):
    '''Given an errno; will raise the matching Python exception
    '''
    raise ErrnoExcepts[errno]

In a function or method that will be calling functions from the C libs:

def _mnt(device, target, fstype, flags, data, libcfile = 'libc.so.0'):

    from ctypes import CDLL, get_errno
    from Teeos.SysUtils.ErrnoExceptions import RaiseErrnoException

    # We can't use find_library() because TeeOS does not have
    # 'gcc', 'ldconfig' or 'objdump'.
    # http://docs.python.org/library/ctypes.html#finding-shared-libraries
    libc = CDLL(libcfile, use_errno = True)
    if libc.mount(device, target, fstype, flags, data):
        RaiseErrnoException(get_errno())
        return False

    return True

Now when using the _mnt() function (used in the Mounter class) if there is a non-zero return from the C libs mount() function, a descriptive Python exception will be raised.

>>> from Teeos.SysUtils.Mount import Mounter
>>> procMount = Mounter(None, '/proc', 'proc', 0, None)
>>> procMount.Mount()
True
>>> procMount.Mount()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/teeos/system-packages/Teeos/SysUtils/Mount.py", line 45, in Mount
    self.fstype, self.mflags, self.data)
  File "/teeos/system-packages/Teeos/SysUtils/Mount.py", line 11, in _mnt
    RaiseErrnoException(get_errno())
  File "/teeos/system-packages/Teeos/SysUtils/ErrnoExceptions.py", line 1181, in RaiseErrnoException
    raise ErrnoExcepts[errno]
Teeos.SysUtils.ErrnoExceptions.EBUSY: 'Device or resource busy'
>>>

The second call to the _mnt() function raised the exception EBUSY, as proc was already mounted.

As long as all 'user facing' classes rely on the underlying system functions like _mnt(), error handling can be in a very 'Pythonic' way with Pythons own exception handling methods.

No comments:

Post a Comment