#!/usr/bin/python3
# -*- coding: utf-8 -*-

# Copyright (c) 2018-2021 Niklas Ekström

import select
import sys
import socket
import time
import os
import struct
import glob
import logging
import json

logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s')
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

try:
    idx = sys.argv.index('-conf-file')
    CONFIG_FILE_PATH = sys.argv[idx + 1]
except (ValueError, IndexError):
    CONFIG_FILE_PATH = 'a314/files_pi/a314fs.conf'

SHARED_DIRECTORY = 'data/a314shared'
METAFILE_EXTENSION = ':a314'

with open(CONFIG_FILE_PATH, encoding='utf-8') as f:
    cfg = json.load(f)
    devs = cfg['devices']
    dev = devs['PI0']
    SHARED_DIRECTORY = dev['path']

MSG_REGISTER_REQ        = 1
MSG_REGISTER_RES        = 2
MSG_DEREGISTER_REQ      = 3
MSG_DEREGISTER_RES      = 4
MSG_READ_MEM_REQ        = 5
MSG_READ_MEM_RES        = 6
MSG_WRITE_MEM_REQ       = 7
MSG_WRITE_MEM_RES       = 8
MSG_CONNECT             = 9
MSG_CONNECT_RESPONSE    = 10
MSG_DATA                = 11
MSG_EOS                 = 12
MSG_RESET               = 13

def wait_for_msg():
    header = b''
    while len(header) < 9:
        data = drv.recv(9 - len(header))
        if not data:
            logger.error('Connection to a314d was closed, terminating.')
            exit(-1)
        header += data
    (plen, stream_id, ptype) = struct.unpack('=IIB', header)
    payload = b''
    while len(payload) < plen:
        data = drv.recv(plen - len(payload))
        if not data:
            logger.error('Connection to a314d was closed, terminating.')
            exit(-1)
        payload += data
    return (stream_id, ptype, payload)

def send_register_req(name):
    m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name
    drv.sendall(m)

def send_read_mem_req(address, length):
    m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length)
    drv.sendall(m)

def read_mem(address, length):
    send_read_mem_req(address, length)
    stream_id, ptype, payload = wait_for_msg()
    if ptype != MSG_READ_MEM_RES:
        logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype)
        exit(-1)
    return payload

def send_write_mem_req(address, data):
    m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data
    drv.sendall(m)

def write_mem(address, data):
    send_write_mem_req(address, data)
    stream_id, ptype, payload = wait_for_msg()
    if ptype != MSG_WRITE_MEM_RES:
        logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype)
        exit(-1)

def send_connect_response(stream_id, result):
    m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result)
    drv.sendall(m)

def send_data(stream_id, data):
    m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data
    drv.sendall(m)

def send_eos(stream_id):
    m = struct.pack('=IIB', 0, stream_id, MSG_EOS)
    drv.sendall(m)

def send_reset(stream_id):
    m = struct.pack('=IIB', 0, stream_id, MSG_RESET)
    drv.sendall(m)

ACTION_NIL              = 0
ACTION_GET_BLOCK        = 2
ACTION_SET_MAP          = 4
ACTION_DIE              = 5
ACTION_EVENT            = 6
ACTION_CURRENT_VOLUME   = 7
ACTION_LOCATE_OBJECT    = 8
ACTION_RENAME_DISK      = 9
ACTION_WRITE            = ord('W')
ACTION_READ             = ord('R')
ACTION_FREE_LOCK        = 15
ACTION_DELETE_OBJECT    = 16
ACTION_RENAME_OBJECT    = 17
ACTION_MORE_CACHE       = 18
ACTION_COPY_DIR         = 19
ACTION_WAIT_CHAR        = 20
ACTION_SET_PROTECT      = 21
ACTION_CREATE_DIR       = 22
ACTION_EXAMINE_OBJECT   = 23
ACTION_EXAMINE_NEXT     = 24
ACTION_DISK_INFO        = 25
ACTION_INFO             = 26
ACTION_FLUSH            = 27
ACTION_SET_COMMENT      = 28
ACTION_PARENT           = 29
ACTION_TIMER            = 30
ACTION_INHIBIT          = 31
ACTION_DISK_TYPE        = 32
ACTION_DISK_CHANGE      = 33
ACTION_SET_DATE         = 34
ACTION_SAME_LOCK        = 40
ACTION_SCREEN_MODE      = 994
ACTION_READ_RETURN      = 1001
ACTION_WRITE_RETURN     = 1002
ACTION_FINDUPDATE       = 1004
ACTION_FINDINPUT        = 1005
ACTION_FINDOUTPUT       = 1006
ACTION_END              = 1007
ACTION_SEEK             = 1008
ACTION_TRUNCATE         = 1022
ACTION_WRITE_PROTECT    = 1023
ACTION_EXAMINE_FH       = 1034
ACTION_UNSUPPORTED      = 65535

ERROR_NO_FREE_STORE             = 103
ERROR_TASK_TABLE_FULL           = 105
ERROR_LINE_TOO_LONG             = 120
ERROR_FILE_NOT_OBJECT           = 121
ERROR_INVALID_RESIDENT_LIBRARY  = 122
ERROR_NO_DEFAULT_DIR            = 201
ERROR_OBJECT_IN_USE             = 202
ERROR_OBJECT_EXISTS             = 203
ERROR_DIR_NOT_FOUND             = 204
ERROR_OBJECT_NOT_FOUND          = 205
ERROR_BAD_STREAM_NAME           = 206
ERROR_OBJECT_TOO_LARGE          = 207
ERROR_ACTION_NOT_KNOWN          = 209
ERROR_INVALID_COMPONENT_NAME    = 210
ERROR_INVALID_LOCK              = 211
ERROR_OBJECT_WRONG_TYPE         = 212
ERROR_DISK_NOT_VALIDATED        = 213
ERROR_DISK_WRITE_PROTECTED      = 214
ERROR_RENAME_ACROSS_DEVICES     = 215
ERROR_DIRECTORY_NOT_EMPTY       = 216
ERROR_TOO_MANY_LEVELS           = 217
ERROR_DEVICE_NOT_MOUNTED        = 218
ERROR_SEEK_ERROR                = 219
ERROR_COMMENT_TOO_BIG           = 220
ERROR_DISK_FULL                 = 221
ERROR_DELETE_PROTECTED          = 222
ERROR_WRITE_PROTECTED           = 223
ERROR_READ_PROTECTED            = 224
ERROR_NOT_A_DOS_DISK            = 225
ERROR_NO_DISK                   = 226
ERROR_NO_MORE_ENTRIES           = 232

SHARED_LOCK         = -2
EXCLUSIVE_LOCK      = -1

LOCK_DIFFERENT      = -1
LOCK_SAME           = 0
LOCK_SAME_VOLUME    = 1

MODE_OLDFILE        = 1005  # Open existing file read/write positioned at beginning of file.
MODE_NEWFILE        = 1006  # Open freshly created file (delete old file) read/write, exclusive lock.
MODE_READWRITE      = 1004  # Open old file w/shared lock, creates file if doesn't exist.

OFFSET_BEGINNING    = -1    # Relative to Begining Of File.
OFFSET_CURRENT      = 0     # Relative to Current file position.
OFFSET_END          = 1     # relative to End Of File.

ST_ROOT             = 1
ST_USERDIR          = 2
ST_SOFTLINK         = 3     # looks like dir, but may point to a file!
ST_LINKDIR          = 4     # hard link to dir
ST_FILE             = -3    # must be negative for FIB!
ST_LINKFILE         = -4    # hard link to file
ST_PIPEFILE         = -5

current_stream_id = 0

class ObjectLock(object):
    def __init__(self, key, mode, path):
        self.key = key
        self.mode = mode
        self.path = path
        self.entry_it = None

locks = {}

next_key = 1

def get_key():
    global next_key
    key = next_key
    next_key = 1 if next_key == 0x7fffffff else (next_key + 1)
    while key in locks:
        key += 1
    return key

def find_path(key, name):
    i = name.find(':')
    if i != -1:
        vol = name[:i].lower()
        if vol == '' or vol == 'pi0' or vol == 'pidisk':
            key = 0
        name = name[i + 1:]

    if key == 0:
        cp = ()
    else:
        cp = locks[key].path

    while name:
        i = name.find('/')
        if i == -1:
            comp = name
            name = ''
        else:
            comp = name[:i]
            name = name[i + 1:]

        if len(comp) == 0:
            if len(cp) == 0:
                return None
            cp = cp[:-1]
        else:
            p = '.' if len(cp) == 0 else '/'.join(cp)
            entries = os.listdir(p)
            found = False
            for e in entries:
                if comp.lower() == e.lower():
                    cp = cp + (e,)
                    found = True
                    break
            if not found:
                if len(name) == 0:
                    cp = cp + (comp,)
                else:
                    return None

    return cp

def read_metadata(path):
    protection = 0
    comment = ''

    if not os.path.isfile(path + METAFILE_EXTENSION):
        return (protection, comment)

    try:
        f = open(path + METAFILE_EXTENSION, 'r')
        for line in f:
            if line[0] == 'p':
                try:
                    protection = int(line[1:].strip())
                except ValueError:
                    pass
            elif line[0] == 'c':
                comment = line[1:].strip()[:79]
        f.close()
    except FileNotFoundError:
        pass
    return (protection, comment)

def write_metadata(path, protection=None, comment=None):
    p, c = read_metadata(path)

    if protection == None:
        protection = p
    if comment == None:
        comment = c

    if (p, c) == (protection, comment):
        return True

    try:
        f = open(path + METAFILE_EXTENSION, 'w')
        f.write('p' + str(protection) + '\n')
        f.write('c' + comment + '\n')
        f.close()
    except FileNotFoundError as e:
        logger.warning('Failed to write metadata for file %s: %s', path, e)
        return False
    return True

def process_locate_object(key, mode, name):
    logger.debug('ACTION_LOCATE_OBJECT, key: %s, mode: %s, name: %s', key, mode, name)

    cp = find_path(key, name)

    if cp is None or not (len(cp) == 0 or os.path.exists('/'.join(cp))):
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    # TODO: Must check if there is already a lock for this path,
    # and if so, if the locks are compatible.

    key = get_key()
    locks[key] = ObjectLock(key, mode, cp)
    return struct.pack('>HHI', 1, 0, key)

def process_free_lock(key):
    logger.debug('ACTION_FREE_LOCK, key: %s', key)
    if key in locks:
        del locks[key]
    return struct.pack('>HH', 1, 0)

def process_copy_dir(prev_key):
    logger.debug('ACTION_COPY_DIR, prev_key: %s', prev_key)
    ol = locks[prev_key]
    key = get_key()
    locks[key] = ObjectLock(key, ol.mode, ol.path)
    return struct.pack('>HHI', 1, 0, key)

def process_parent(prev_key):
    logger.debug('ACTION_PARENT, prev_key: %s', prev_key)
    ol = locks[prev_key]
    if len(ol.path) == 0:
        key = 0
    else:
        key = get_key()
        locks[key] = ObjectLock(key, SHARED_LOCK, ol.path[:-1])
    return struct.pack('>HHI', 1, 0, key)

def mtime_to_dmt(mtime):
    mtime = int(mtime)
    days = mtime // 86400
    left = mtime - days * 86400
    mins = left // 60
    secs = left - mins * 60
    ticks = secs * 50
    days -= 2922 # Days between 1970-01-01 and 1978-01-01
    days = max(0, days) # If days are before Amiga epoc
    return (days, mins, ticks)

def process_examine_object(key):
    logger.debug('ACTION_EXAMINE_OBJECT, key: %s', key)
    ol = locks[key]

    if len(ol.path) == 0:
        fn = 'PiDisk'
        path = '.'
    else:
        fn = ol.path[-1]
        path = '/'.join(ol.path)

    days, mins, ticks = mtime_to_dmt(os.path.getmtime(path))
    protection, comment = read_metadata(path)

    if os.path.isfile(path):
        size = os.path.getsize(path)
        type_ = ST_FILE
    else:
        size = 0
        type_ = ST_USERDIR
        ol.entry_it = os.scandir(path)

    size = min(size, 2 ** 31 - 1)
    fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore')
    comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore')
    return struct.pack('>HHHhIIIII', 1, 0, 666, type_, size, protection, days, mins, ticks) + fn + comment

def process_examine_next(key, disk_key):
    logger.debug('ACTION_EXAMINE_NEXT, key: %s, disk_key: %s', key, disk_key)
    ol = locks[key]

    if len(ol.path) == 0:
        path = '.'
    else:
        path = '/'.join(ol.path)

    if not os.path.isdir(path):
        return struct.pack('>HH', 0, ERROR_OBJECT_WRONG_TYPE)

    disk_key += 1

    entry = next(ol.entry_it, None)
    while entry and entry.name.endswith(METAFILE_EXTENSION):
        entry = next(ol.entry_it, None)

    if not entry:
        return struct.pack('>HH', 0, ERROR_NO_MORE_ENTRIES)

    fn = entry.name
    path = ('/'.join(ol.path + (fn,)))

    days, mins, ticks = mtime_to_dmt(entry.stat().st_mtime)
    protection, comment = read_metadata(path)

    if os.path.isfile(path):
        size = os.path.getsize(path)
        type_ = ST_FILE
    else:
        size = 0
        type_ = ST_USERDIR

    size = min(size, 2 ** 31 - 1)
    fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore')
    comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore')
    return struct.pack('>HHHhIIIII', 1, 0, disk_key, type_, size, protection, days, mins, ticks) + fn + comment

def process_examine_fh(arg1):
    logger.debug('ACTION_EXAMINE_FH, arg1: %s', arg1)

    fn = open_file_handles[arg1].f.name
    path = os.path.realpath(fn)
    days, mins, ticks = mtime_to_dmt(os.path.getmtime(path))
    protection, comment = read_metadata(path)

    if os.path.isfile(path):
        size = os.path.getsize(path)
        type_ = ST_FILE
    else:
        size = 0
        type_ = ST_USERDIR

    size = min(size, 2 ** 31 - 1)
    fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore')
    comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore')
    return struct.pack('>HHHhIIIII', 1, 0, 666, type_, size, protection, days, mins, ticks) + fn + comment


next_fp = 1

open_file_handles = {}

def get_file_ptr():
    global next_fp
    fp = next_fp
    next_fp = 1 if next_fp == 0x7fffffff else next_fp + 1
    while fp in open_file_handles:
        fp += 1
    return fp

class OpenFileHandle(object):
    def __init__(self, fp, f, p):
        self.fp = fp
        self.f = f
        self.p = p

def process_findxxx(mode, key, name):
    if mode == ACTION_FINDINPUT:
        logger.debug('ACTION_FINDINPUT, key: %s, name: %s', key, name)
    elif mode == ACTION_FINDOUTPUT:
        logger.debug('ACTION_FINDOUTPUT, key: %s, name: %s', key, name)
    elif mode == ACTION_FINDUPDATE:
        logger.debug('ACTION_FINDUPDATE, key: %s, name: %s', key, name)

    cp = find_path(key, name)
    if cp is None:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    path = '/'.join(cp)
    if len(cp) == 0 or os.path.isdir(path):
        return struct.pack('>HH', 0, ERROR_OBJECT_WRONG_TYPE)

    # TODO: Must check if there already exists a non-compatible lock for this path.

    # TODO: This must be handled better. Especially error reporting.

    protection, _ = read_metadata(path)
    try:
        if mode == MODE_OLDFILE:
            f = open(path, 'r+b')
        elif mode == MODE_READWRITE:
            f = open(path, 'r+b')
            if protection & 16:
                protection = protection & 0b11101111
                write_metadata(path, protection=protection)
        elif mode == MODE_NEWFILE:
            if protection & 0x1:
                return struct.pack('>HH', 0, ERROR_DELETE_PROTECTED)
            elif protection & 0x4:
                return struct.pack('>HH', 0, ERROR_WRITE_PROTECTED)
            f = open(path, 'w+b')
            if protection & 16:
                protection = protection & 0b11101111
                write_metadata(path, protection=protection)
    except IOError:
        if mode == MODE_READWRITE:
            try:
                f = open(path, 'w+b')
                if protection & 16:
                    protection = protection & 0b11101111
                    write_metadata(path, protection=protection)
            except IOError:
                return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
        else:
            return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    fp = get_file_ptr()
    ofh = OpenFileHandle(fp, f, protection)
    open_file_handles[fp] = ofh

    return struct.pack('>HHI', 1, 0, fp)

def process_read(arg1, address, length):
    logger.debug('ACTION_READ, arg1: %s, address: %s, length: %s', arg1, address, length)
    protection = open_file_handles[arg1].p
    if protection & 0x8:
        return struct.pack('>HH', 0, ERROR_READ_PROTECTED)
    f = open_file_handles[arg1].f
    data = f.read(length)
    if len(data) != 0:
        write_mem(address, data)
    return struct.pack('>HHI', 1, 0, len(data))

def process_write(arg1, address, length):
    logger.debug('ACTION_WRITE, arg1: %s, address: %s, length: %s', arg1, address, length)
    protection = open_file_handles[arg1].p
    if protection & 0x4:
        return struct.pack('>HH', 0, ERROR_WRITE_PROTECTED)
    data = read_mem(address, length)
    f = open_file_handles[arg1].f
    f.seek(0, 1)
    try:
        f.write(data)
    except IOError:
        return struct.pack('>HH', 0, ERROR_DISK_FULL)
    return struct.pack('>HHI', 1, 0, length)

def process_seek(arg1, new_pos, mode):
    logger.debug('ACTION_SEEK, arg1: %s, new_pos: %s, mode: %s', arg1, new_pos, mode)

    f = open_file_handles[arg1].f
    old_pos = f.tell()

    from_what = 0
    if mode == OFFSET_CURRENT:
        from_what = 1
    elif mode == OFFSET_END:
        from_what = 2

    f.seek(new_pos, from_what)

    return struct.pack('>HHi', 1, 0, old_pos)

def process_end(arg1):
    logger.debug('ACTION_END, arg1: %s', arg1)

    if arg1 in open_file_handles:
        f = open_file_handles.pop(arg1).f
        f.close()

    return struct.pack('>HH', 1, 0)

def process_delete_object(key, name):
    logger.debug('ACTION_DELETE_OBJECT, key: %s, name: %s', key, name)

    cp = find_path(key, name)
    if cp is None or len(cp) == 0:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    path = '/'.join(cp)
    is_dir = os.path.isdir(path)

    protection, _ = read_metadata(path)
    if protection & 0x1:
        return struct.pack('>HH', 0, ERROR_DELETE_PROTECTED)

    try:
        if is_dir:
            os.rmdir(path)
        else:
            os.remove(path)
        if os.path.isfile(path + METAFILE_EXTENSION):
            os.remove(path + METAFILE_EXTENSION)
    except:
        if is_dir:
            return struct.pack('>HH', 0, ERROR_DIRECTORY_NOT_EMPTY)
        else:
            return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    return struct.pack('>HH', 1, 0)

def process_rename_object(key, name, target_dir, new_name):
    logger.debug('ACTION_RENAME_OBJECT, key: %s, name: %s, target_dir: %s, new_name: %s', key, name, target_dir, new_name)

    cp1 = find_path(key, name)
    if cp1 is None or len(cp1) == 0:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    from_path = '/'.join(cp1)
    if not os.path.exists(from_path):
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    cp2 = find_path(target_dir, new_name)
    if cp2 is None or len(cp2) == 0:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    to_path = '/'.join(cp2)

    if from_path == to_path:
        return struct.pack('>HH', 1, 0)

    if os.path.exists(to_path):
        return struct.pack('>HH', 0, ERROR_OBJECT_EXISTS)

    try:
        os.rename(from_path, to_path)
        if os.path.isfile(from_path + METAFILE_EXTENSION):
            os.rename(from_path + METAFILE_EXTENSION, to_path + METAFILE_EXTENSION)
    except:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    return struct.pack('>HH', 1, 0)

def process_create_dir(key, name):
    logger.debug('ACTION_CREATE_DIR, key: %s, name: %s', key, name)

    cp = find_path(key, name)
    if cp is None or len(cp) == 0:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    try:
        path = '/'.join(cp)
        os.makedirs(path)
    except:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    key = get_key()
    locks[key] = ObjectLock(key, SHARED_LOCK, cp)
    return struct.pack('>HHI', 1, 0, key)

def process_set_protect(key, name, mask):
    logger.debug('ACTION_SET_PROTECT, key: %s, name: %s, mask: %s', key, name, mask)

    cp = find_path(key, name)
    if cp is None or len(cp) == 0:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    path = '/'.join(cp)
    if write_metadata(path, protection=mask):
        return struct.pack('>HH', 1, 0)
    else:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

def process_set_comment(key, name, comment):
    logger.debug('ACTION_SET_COMMENT, key: %s, name: %s, comment: %s', key, name, comment)

    if len(comment) > 79:
        return struct.pack('>HH', 0, ERROR_COMMENT_TOO_BIG)

    cp = find_path(key, name)
    if cp is None or len(cp) == 0:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

    path = '/'.join(cp)
    if write_metadata(path, comment=comment):
        return struct.pack('>HH', 1, 0)
    else:
        return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)

def process_same_lock(key1, key2):
    logger.debug('ACTION_SAME_LOCK, key1: %s key2: %s', key1, key2)

    if not (key1 in locks and key2 in locks):
        return struct.pack('>HH', 0, LOCK_DIFFERENT)
    elif locks[key1].path == locks[key2].path:
        return struct.pack('>HH', 1, LOCK_SAME)
    else:
        return struct.pack('>HH', 0, LOCK_SAME_VOLUME)

def process_request(req):
    #logger.debug('len(req): %s, req: %s', len(req), list(req))

    (rtype,) = struct.unpack('>H', req[:2])

    if rtype == ACTION_LOCATE_OBJECT:
        key, mode, nlen = struct.unpack('>IHB', req[2:9])
        name = req[9:9+nlen].decode('latin-1')
        return process_locate_object(key, mode, name)
    elif rtype == ACTION_FREE_LOCK:
        (key,) = struct.unpack('>I', req[2:6])
        return process_free_lock(key)
    elif rtype == ACTION_COPY_DIR:
        (key,) = struct.unpack('>I', req[2:6])
        return process_copy_dir(key)
    elif rtype == ACTION_PARENT:
        (key,) = struct.unpack('>I', req[2:6])
        return process_parent(key)
    elif rtype == ACTION_EXAMINE_OBJECT:
        (key,) = struct.unpack('>I', req[2:6])
        return process_examine_object(key)
    elif rtype == ACTION_EXAMINE_NEXT:
        key, disk_key = struct.unpack('>IH', req[2:8])
        return process_examine_next(key, disk_key)
    elif rtype == ACTION_EXAMINE_FH:
        (arg1,) = struct.unpack('>I', req[2:6])
        return process_examine_fh(arg1)
    elif rtype == ACTION_FINDINPUT or rtype == ACTION_FINDOUTPUT or rtype == ACTION_FINDUPDATE:
        key, nlen = struct.unpack('>IB', req[2:7])
        name = req[7:7+nlen].decode('latin-1')
        return process_findxxx(rtype, key, name)
    elif rtype == ACTION_READ:
        arg1, address, length = struct.unpack('>III', req[2:14])
        return process_read(arg1, address, length)
    elif rtype == ACTION_WRITE:
        arg1, address, length = struct.unpack('>III', req[2:14])
        return process_write(arg1, address, length)
    elif rtype == ACTION_SEEK:
        arg1, new_pos, mode = struct.unpack('>Iii', req[2:14])
        return process_seek(arg1, new_pos, mode)
    elif rtype == ACTION_END:
        (arg1,) = struct.unpack('>I', req[2:6])
        return process_end(arg1)
    elif rtype == ACTION_DELETE_OBJECT:
        key, nlen = struct.unpack('>IB', req[2:7])
        name = req[7:7+nlen].decode('latin-1')
        return process_delete_object(key, name)
    elif rtype == ACTION_RENAME_OBJECT:
        key, target_dir, nlen, nnlen = struct.unpack('>IIBB', req[2:12])
        name = req[12:12+nlen].decode('latin-1')
        new_name = req[12+nlen:12+nlen+nnlen].decode('latin-1')
        return process_rename_object(key, name, target_dir, new_name)
    elif rtype == ACTION_CREATE_DIR:
        key, nlen = struct.unpack('>IB', req[2:7])
        name = req[7:7+nlen].decode('latin-1')
        return process_create_dir(key, name)
    elif rtype == ACTION_SET_PROTECT:
        key, mask, nlen = struct.unpack('>IIB', req[2:11])
        name = req[11:11+nlen].decode('latin-1')
        return process_set_protect(key, name, mask)
    elif rtype == ACTION_SET_COMMENT:
        key, nlen, clen = struct.unpack('>IBB', req[2:8])
        name = req[8:8+nlen].decode('latin-1')
        comment = req[8+nlen:8+nlen+clen].decode('latin-1')
        return process_set_comment(key, name, comment)
    elif rtype == ACTION_SAME_LOCK:
        key1, key2 = struct.unpack('>II', req[2:10])
        return process_same_lock(key1, key2)
    elif rtype == ACTION_UNSUPPORTED:
        (dp_Type,) = struct.unpack('>H', req[2:4])
        logger.warning('Unsupported action %d (Amiga/a314fs)', dp_Type)
        return struct.pack('>HH', 0, ERROR_ACTION_NOT_KNOWN)
    else:
        logger.warning('Unsupported action %d (a314d/a314fs)', rtype)
        return struct.pack('>HH', 0, ERROR_ACTION_NOT_KNOWN)

done = False

try:
    idx = sys.argv.index('-ondemand')
except ValueError:
    idx = -1

if idx != -1:
    fd = int(sys.argv[idx + 1])
    drv = socket.socket(fileno=fd)
else:
    drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    drv.connect(('localhost', 7110))

    send_register_req(b'a314fs')
    stream_id, ptype, payload = wait_for_msg()
    if payload[0] != 1:
        logger.error('Unable to register service a314fs, shutting down')
        drv.close()
        done = True

if not done:
    os.chdir(SHARED_DIRECTORY)
    logger.info('a314fs is running, shared directory: %s', SHARED_DIRECTORY)

while not done:
    stream_id, ptype, payload = wait_for_msg()

    if ptype == MSG_CONNECT:
        if payload == b'a314fs':
            if current_stream_id is not None:
                send_reset(current_stream_id)
            current_stream_id = stream_id
            send_connect_response(stream_id, 0)
        else:
            send_connect_response(stream_id, 3)
    elif ptype == MSG_DATA:
        address, length = struct.unpack('>II', payload)
        #logger.debug('address: %s, length: %s', address, length)
        req = read_mem(address + 2, length - 2)
        res = process_request(req)
        write_mem(address + 2, res)
        #write_mem(address, b'\xff\xff')
        send_data(stream_id, b'\xff\xff')
    elif ptype == MSG_EOS:
        if stream_id == current_stream_id:
            logger.debug('Got EOS, stream closed')
            send_eos(stream_id)
            current_stream_id = None
    elif ptype == MSG_RESET:
        if stream_id == current_stream_id:
            logger.debug('Got RESET, stream closed')
            current_stream_id = None
