Newer
Older
barebox / scripts / remote / ratpfs.py
@Jan Luebbe Jan Luebbe on 18 Jan 2016 6 KB host side for barebox remote control
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function

import logging
import os
import stat
import struct
from enum import IntEnum

from .messages import BBPacketFS, BBPacketFSReturn

class RatpFSType(IntEnum):
    invalid = 0
    mount_call = 1
    mount_return = 2
    readdir_call = 3
    readdir_return = 4
    stat_call = 5
    stat_return = 6
    open_call = 7
    open_return = 8
    read_call = 9
    read_return = 10
    write_call = 11
    write_return = 12
    close_call = 13
    close_return = 14
    truncate_call = 15
    truncate_return = 16


class RatpFSError(ValueError):
    pass


class RatpFSPacket(object):
    def __init__(self, type=RatpFSType.invalid, payload="", raw=None):
        if raw is not None:
            type, = struct.unpack('!B', raw[:1])
            self.type = RatpFSType(type)
            self.payload = raw[1:]
        else:
            self.type = type
            self.payload = payload

    def __repr__(self):
        s = "%s(" % self.__class__.__name__
        s += "TYPE=%i," % self.type
        s += "PAYLOAD=%s)" % repr(self.payload)
        return s

    def pack(self):
        return struct.pack('!B', int(self.type))+self.payload


class RatpFSServer(object):
    def __init__(self, path=None):
        self.path = path
        if path:
            self.path = os.path.abspath(os.path.expanduser(path))
        self.next_handle = 1  # 0 is invalid
        self.files = {}
        self.mounted = False
        logging.info("exporting: %s", self.path)

    def _alloc_handle(self):
        handle = self.next_handle
        self.next_handle += 1
        return handle

    def _resolve(self, path):
        components = path.split('/')
        components = [x for x in components if x and x != '..']
        return os.path.join(self.path, *components)

    def handle_stat(self, path):

        try:
            logging.info("path: %r", path)
            path = self._resolve(path)
            logging.info("path1: %r", path)
            s = os.stat(path)
        except OSError as e:
            return struct.pack('!BI', 0, e.errno)
        if stat.S_ISREG(s.st_mode):
            return struct.pack('!BI', 1, s.st_size)
        elif stat.S_ISDIR(s.st_mode):
            return struct.pack('!BI', 2, s.st_size)
        else:
            return struct.pack('!BI', 0, 0)

    def handle_open(self, params):
        flags, = struct.unpack('!I', params[:4])
        flags = flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR | os.O_CREAT |
                         os.O_TRUNC)
        path = params[4:]
        try:
            f = os.open(self._resolve(path), flags, 0666)
        except OSError as e:
            return struct.pack('!II', 0, e.errno)
        h = self._alloc_handle()
        self.files[h] = f
        size = os.lseek(f, 0, os.SEEK_END)
        return struct.pack('!II', h, size)

    def handle_read(self, params):
        h, pos, size = struct.unpack('!III', params)
        f = self.files[h]
        os.lseek(f, pos, os.SEEK_SET)
        size = min(size, 4096)
        return os.read(f, size)

    def handle_write(self, params):
        h, pos = struct.unpack('!II', params[:8])
        payload = params[8:]
        f = self.files[h]
        pos = os.lseek(f, pos, os.SEEK_SET)
        assert os.write(f, payload) == len(payload)
        return ""

    def handle_readdir(self, path):
        res = ""
        for x in os.listdir(self._resolve(path)):
            res += x+'\0'
        return res

    def handle_close(self, params):
        h, = struct.unpack('!I', params[:4])
        os.close(self.files.pop(h))
        return ""

    def handle_truncate(self, params):
        h, size = struct.unpack('!II', params)
        f = self.files[h]
        os.ftruncate(f, size)
        return ""

    def handle(self, bbcall):
        assert isinstance(bbcall, BBPacketFS)
        logging.debug("bb-call: %s", bbcall)
        fscall = RatpFSPacket(raw=bbcall.payload)
        logging.info("fs-call: %s", fscall)

        if not self.path:
            logging.warning("no filesystem exported")
            fsreturn = RatpFSPacket(type=RatpFSType.invalid)
        elif fscall.type == RatpFSType.mount_call:
            self.mounted = True
            fsreturn = RatpFSPacket(type=RatpFSType.mount_return)
        elif not self.mounted:
            logging.warning("filesystem not mounted")
            fsreturn = RatpFSPacket(type=RatpFSType.invalid)
        elif fscall.type == RatpFSType.readdir_call:
            payload = self.handle_readdir(fscall.payload)
            fsreturn = RatpFSPacket(type=RatpFSType.readdir_return,
                                    payload=payload)
        elif fscall.type == RatpFSType.stat_call:
            payload = self.handle_stat(fscall.payload)
            fsreturn = RatpFSPacket(type=RatpFSType.stat_return,
                                    payload=payload)
        elif fscall.type == RatpFSType.open_call:
            payload = self.handle_open(fscall.payload)
            fsreturn = RatpFSPacket(type=RatpFSType.open_return,
                                    payload=payload)
        elif fscall.type == RatpFSType.read_call:
            payload = self.handle_read(fscall.payload)
            fsreturn = RatpFSPacket(type=RatpFSType.read_return,
                                    payload=payload)
        elif fscall.type == RatpFSType.write_call:
            payload = self.handle_write(fscall.payload)
            fsreturn = RatpFSPacket(type=RatpFSType.write_return,
                                    payload=payload)
        elif fscall.type == RatpFSType.close_call:
            payload = self.handle_close(fscall.payload)
            fsreturn = RatpFSPacket(type=RatpFSType.close_return,
                                    payload=payload)
        elif fscall.type == RatpFSType.truncate_call:
            payload = self.handle_truncate(fscall.payload)
            fsreturn = RatpFSPacket(type=RatpFSType.truncate_return,
                                    payload=payload)
        else:
            raise RatpFSError()

        logging.info("fs-return: %s", fsreturn)
        bbreturn = BBPacketFSReturn(payload=fsreturn.pack())
        logging.debug("bb-return: %s", bbreturn)
        return bbreturn