]> git.sesse.net Git - pistorm/blob - a314/files_pi/a314fs.py
d07275b65d3a82ee1de1e74b0b047ebed3d2ad37
[pistorm] / a314 / files_pi / a314fs.py
1 #!/usr/bin/python3
2 # -*- coding: utf-8 -*-
3
4 # Copyright (c) 2018-2021 Niklas Ekström
5
6 import select
7 import sys
8 import socket
9 import time
10 import os
11 import struct
12 import glob
13 import logging
14 import json
15
16 logging.basicConfig(format = '%(levelname)s, %(asctime)s, %(name)s, line %(lineno)d: %(message)s')
17 logger = logging.getLogger(__name__)
18 logger.setLevel(logging.INFO)
19
20 CONFIG_FILE_PATH = 'a314/files_pi/a314fs.conf'
21
22 SHARED_DIRECTORY = 'data/a314shared'
23 METAFILE_EXTENSION = ':a314'
24
25 with open(CONFIG_FILE_PATH, encoding='utf-8') as f:
26     cfg = json.load(f)
27     devs = cfg['devices']
28     dev = devs['PI0']
29     SHARED_DIRECTORY = dev['path']
30
31 MSG_REGISTER_REQ        = 1
32 MSG_REGISTER_RES        = 2
33 MSG_DEREGISTER_REQ      = 3
34 MSG_DEREGISTER_RES      = 4
35 MSG_READ_MEM_REQ        = 5
36 MSG_READ_MEM_RES        = 6
37 MSG_WRITE_MEM_REQ       = 7
38 MSG_WRITE_MEM_RES       = 8
39 MSG_CONNECT             = 9
40 MSG_CONNECT_RESPONSE    = 10
41 MSG_DATA                = 11
42 MSG_EOS                 = 12
43 MSG_RESET               = 13
44
45 def wait_for_msg():
46     header = b''
47     while len(header) < 9:
48         data = drv.recv(9 - len(header))
49         if not data:
50             logger.error('Connection to a314d was closed, terminating.')
51             exit(-1)
52         header += data
53     (plen, stream_id, ptype) = struct.unpack('=IIB', header)
54     payload = b''
55     while len(payload) < plen:
56         data = drv.recv(plen - len(payload))
57         if not data:
58             logger.error('Connection to a314d was closed, terminating.')
59             exit(-1)
60         payload += data
61     return (stream_id, ptype, payload)
62
63 def send_register_req(name):
64     m = struct.pack('=IIB', len(name), 0, MSG_REGISTER_REQ) + name
65     drv.sendall(m)
66
67 def send_read_mem_req(address, length):
68     m = struct.pack('=IIBII', 8, 0, MSG_READ_MEM_REQ, address, length)
69     drv.sendall(m)
70
71 def read_mem(address, length):
72     send_read_mem_req(address, length)
73     stream_id, ptype, payload = wait_for_msg()
74     if ptype != MSG_READ_MEM_RES:
75         logger.error('Expected MSG_READ_MEM_RES but got %s. Shutting down.', ptype)
76         exit(-1)
77     return payload
78
79 def send_write_mem_req(address, data):
80     m = struct.pack('=IIBI', 4 + len(data), 0, MSG_WRITE_MEM_REQ, address) + data
81     drv.sendall(m)
82
83 def write_mem(address, data):
84     send_write_mem_req(address, data)
85     stream_id, ptype, payload = wait_for_msg()
86     if ptype != MSG_WRITE_MEM_RES:
87         logger.error('Expected MSG_WRITE_MEM_RES but got %s. Shutting down.', ptype)
88         exit(-1)
89
90 def send_connect_response(stream_id, result):
91     m = struct.pack('=IIBB', 1, stream_id, MSG_CONNECT_RESPONSE, result)
92     drv.sendall(m)
93
94 def send_data(stream_id, data):
95     m = struct.pack('=IIB', len(data), stream_id, MSG_DATA) + data
96     drv.sendall(m)
97
98 def send_eos(stream_id):
99     m = struct.pack('=IIB', 0, stream_id, MSG_EOS)
100     drv.sendall(m)
101
102 def send_reset(stream_id):
103     m = struct.pack('=IIB', 0, stream_id, MSG_RESET)
104     drv.sendall(m)
105
106 ACTION_NIL              = 0
107 ACTION_GET_BLOCK        = 2
108 ACTION_SET_MAP          = 4
109 ACTION_DIE              = 5
110 ACTION_EVENT            = 6
111 ACTION_CURRENT_VOLUME   = 7
112 ACTION_LOCATE_OBJECT    = 8
113 ACTION_RENAME_DISK      = 9
114 ACTION_WRITE            = ord('W')
115 ACTION_READ             = ord('R')
116 ACTION_FREE_LOCK        = 15
117 ACTION_DELETE_OBJECT    = 16
118 ACTION_RENAME_OBJECT    = 17
119 ACTION_MORE_CACHE       = 18
120 ACTION_COPY_DIR         = 19
121 ACTION_WAIT_CHAR        = 20
122 ACTION_SET_PROTECT      = 21
123 ACTION_CREATE_DIR       = 22
124 ACTION_EXAMINE_OBJECT   = 23
125 ACTION_EXAMINE_NEXT     = 24
126 ACTION_DISK_INFO        = 25
127 ACTION_INFO             = 26
128 ACTION_FLUSH            = 27
129 ACTION_SET_COMMENT      = 28
130 ACTION_PARENT           = 29
131 ACTION_TIMER            = 30
132 ACTION_INHIBIT          = 31
133 ACTION_DISK_TYPE        = 32
134 ACTION_DISK_CHANGE      = 33
135 ACTION_SET_DATE         = 34
136 ACTION_SAME_LOCK        = 40
137 ACTION_SCREEN_MODE      = 994
138 ACTION_READ_RETURN      = 1001
139 ACTION_WRITE_RETURN     = 1002
140 ACTION_FINDUPDATE       = 1004
141 ACTION_FINDINPUT        = 1005
142 ACTION_FINDOUTPUT       = 1006
143 ACTION_END              = 1007
144 ACTION_SEEK             = 1008
145 ACTION_TRUNCATE         = 1022
146 ACTION_WRITE_PROTECT    = 1023
147 ACTION_EXAMINE_FH       = 1034
148 ACTION_UNSUPPORTED      = 65535
149
150 ERROR_NO_FREE_STORE             = 103
151 ERROR_TASK_TABLE_FULL           = 105
152 ERROR_LINE_TOO_LONG             = 120
153 ERROR_FILE_NOT_OBJECT           = 121
154 ERROR_INVALID_RESIDENT_LIBRARY  = 122
155 ERROR_NO_DEFAULT_DIR            = 201
156 ERROR_OBJECT_IN_USE             = 202
157 ERROR_OBJECT_EXISTS             = 203
158 ERROR_DIR_NOT_FOUND             = 204
159 ERROR_OBJECT_NOT_FOUND          = 205
160 ERROR_BAD_STREAM_NAME           = 206
161 ERROR_OBJECT_TOO_LARGE          = 207
162 ERROR_ACTION_NOT_KNOWN          = 209
163 ERROR_INVALID_COMPONENT_NAME    = 210
164 ERROR_INVALID_LOCK              = 211
165 ERROR_OBJECT_WRONG_TYPE         = 212
166 ERROR_DISK_NOT_VALIDATED        = 213
167 ERROR_DISK_WRITE_PROTECTED      = 214
168 ERROR_RENAME_ACROSS_DEVICES     = 215
169 ERROR_DIRECTORY_NOT_EMPTY       = 216
170 ERROR_TOO_MANY_LEVELS           = 217
171 ERROR_DEVICE_NOT_MOUNTED        = 218
172 ERROR_SEEK_ERROR                = 219
173 ERROR_COMMENT_TOO_BIG           = 220
174 ERROR_DISK_FULL                 = 221
175 ERROR_DELETE_PROTECTED          = 222
176 ERROR_WRITE_PROTECTED           = 223
177 ERROR_READ_PROTECTED            = 224
178 ERROR_NOT_A_DOS_DISK            = 225
179 ERROR_NO_DISK                   = 226
180 ERROR_NO_MORE_ENTRIES           = 232
181
182 SHARED_LOCK         = -2
183 EXCLUSIVE_LOCK      = -1
184
185 LOCK_DIFFERENT      = -1
186 LOCK_SAME           = 0
187 LOCK_SAME_VOLUME    = 1
188
189 MODE_OLDFILE        = 1005  # Open existing file read/write positioned at beginning of file.
190 MODE_NEWFILE        = 1006  # Open freshly created file (delete old file) read/write, exclusive lock.
191 MODE_READWRITE      = 1004  # Open old file w/shared lock, creates file if doesn't exist.
192
193 OFFSET_BEGINNING    = -1    # Relative to Begining Of File.
194 OFFSET_CURRENT      = 0     # Relative to Current file position.
195 OFFSET_END          = 1     # relative to End Of File.
196
197 ST_ROOT             = 1
198 ST_USERDIR          = 2
199 ST_SOFTLINK         = 3     # looks like dir, but may point to a file!
200 ST_LINKDIR          = 4     # hard link to dir
201 ST_FILE             = -3    # must be negative for FIB!
202 ST_LINKFILE         = -4    # hard link to file
203 ST_PIPEFILE         = -5
204
205 current_stream_id = 0
206
207 class ObjectLock(object):
208     def __init__(self, key, mode, path):
209         self.key = key
210         self.mode = mode
211         self.path = path
212         self.entry_it = None
213
214 locks = {}
215
216 next_key = 1
217
218 def get_key():
219     global next_key
220     key = next_key
221     next_key = 1 if next_key == 0x7fffffff else (next_key + 1)
222     while key in locks:
223         key += 1
224     return key
225
226 def find_path(key, name):
227     i = name.find(':')
228     if i != -1:
229         vol = name[:i].lower()
230         if vol == '' or vol == 'pi0' or vol == 'pidisk':
231             key = 0
232         name = name[i + 1:]
233
234     if key == 0:
235         cp = ()
236     else:
237         cp = locks[key].path
238
239     while name:
240         i = name.find('/')
241         if i == -1:
242             comp = name
243             name = ''
244         else:
245             comp = name[:i]
246             name = name[i + 1:]
247
248         if len(comp) == 0:
249             if len(cp) == 0:
250                 return None
251             cp = cp[:-1]
252         else:
253             p = '.' if len(cp) == 0 else '/'.join(cp)
254             entries = os.listdir(p)
255             found = False
256             for e in entries:
257                 if comp.lower() == e.lower():
258                     cp = cp + (e,)
259                     found = True
260                     break
261             if not found:
262                 if len(name) == 0:
263                     cp = cp + (comp,)
264                 else:
265                     return None
266
267     return cp
268
269 def read_metadata(path):
270     protection = 0
271     comment = ''
272
273     if not os.path.isfile(path + METAFILE_EXTENSION):
274         return (protection, comment)
275
276     try:
277         f = open(path + METAFILE_EXTENSION, 'r')
278         for line in f:
279             if line[0] == 'p':
280                 try:
281                     protection = int(line[1:].strip())
282                 except ValueError:
283                     pass
284             elif line[0] == 'c':
285                 comment = line[1:].strip()[:79]
286         f.close()
287     except FileNotFoundError:
288         pass
289     return (protection, comment)
290
291 def write_metadata(path, protection=None, comment=None):
292     p, c = read_metadata(path)
293
294     if protection == None:
295         protection = p
296     if comment == None:
297         comment = c
298
299     if (p, c) == (protection, comment):
300         return True
301
302     try:
303         f = open(path + METAFILE_EXTENSION, 'w')
304         f.write('p' + str(protection) + '\n')
305         f.write('c' + comment + '\n')
306         f.close()
307     except FileNotFoundError as e:
308         logger.warning('Failed to write metadata for file %s: %s', path, e)
309         return False
310     return True
311
312 def process_locate_object(key, mode, name):
313     logger.debug('ACTION_LOCATE_OBJECT, key: %s, mode: %s, name: %s', key, mode, name)
314
315     cp = find_path(key, name)
316
317     if cp is None or not (len(cp) == 0 or os.path.exists('/'.join(cp))):
318         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
319
320     # TODO: Must check if there is already a lock for this path,
321     # and if so, if the locks are compatible.
322
323     key = get_key()
324     locks[key] = ObjectLock(key, mode, cp)
325     return struct.pack('>HHI', 1, 0, key)
326
327 def process_free_lock(key):
328     logger.debug('ACTION_FREE_LOCK, key: %s', key)
329     if key in locks:
330         del locks[key]
331     return struct.pack('>HH', 1, 0)
332
333 def process_copy_dir(prev_key):
334     logger.debug('ACTION_COPY_DIR, prev_key: %s', prev_key)
335     ol = locks[prev_key]
336     key = get_key()
337     locks[key] = ObjectLock(key, ol.mode, ol.path)
338     return struct.pack('>HHI', 1, 0, key)
339
340 def process_parent(prev_key):
341     logger.debug('ACTION_PARENT, prev_key: %s', prev_key)
342     ol = locks[prev_key]
343     if len(ol.path) == 0:
344         key = 0
345     else:
346         key = get_key()
347         locks[key] = ObjectLock(key, SHARED_LOCK, ol.path[:-1])
348     return struct.pack('>HHI', 1, 0, key)
349
350 def mtime_to_dmt(mtime):
351     mtime = int(mtime)
352     days = mtime // 86400
353     left = mtime - days * 86400
354     mins = left // 60
355     secs = left - mins * 60
356     ticks = secs * 50
357     days -= 2922 # Days between 1970-01-01 and 1978-01-01
358     days = max(0, days) # If days are before Amiga epoc
359     return (days, mins, ticks)
360
361 def process_examine_object(key):
362     logger.debug('ACTION_EXAMINE_OBJECT, key: %s', key)
363     ol = locks[key]
364
365     if len(ol.path) == 0:
366         fn = 'PiDisk'
367         path = '.'
368     else:
369         fn = ol.path[-1]
370         path = '/'.join(ol.path)
371
372     days, mins, ticks = mtime_to_dmt(os.path.getmtime(path))
373     protection, comment = read_metadata(path)
374
375     if os.path.isfile(path):
376         size = os.path.getsize(path)
377         type_ = ST_FILE
378     else:
379         size = 0
380         type_ = ST_USERDIR
381         ol.entry_it = os.scandir(path)
382
383     size = min(size, 2 ** 31 - 1)
384     fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore')
385     comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore')
386     return struct.pack('>HHHhIIIII', 1, 0, 666, type_, size, protection, days, mins, ticks) + fn + comment
387
388 def process_examine_next(key, disk_key):
389     logger.debug('ACTION_EXAMINE_NEXT, key: %s, disk_key: %s', key, disk_key)
390     ol = locks[key]
391
392     if len(ol.path) == 0:
393         path = '.'
394     else:
395         path = '/'.join(ol.path)
396
397     if not os.path.isdir(path):
398         return struct.pack('>HH', 0, ERROR_OBJECT_WRONG_TYPE)
399
400     disk_key += 1
401
402     entry = next(ol.entry_it, None)
403     while entry and entry.name.endswith(METAFILE_EXTENSION):
404         entry = next(ol.entry_it, None)
405
406     if not entry:
407         return struct.pack('>HH', 0, ERROR_NO_MORE_ENTRIES)
408
409     fn = entry.name
410     path = ('/'.join(ol.path + (fn,)))
411
412     days, mins, ticks = mtime_to_dmt(entry.stat().st_mtime)
413     protection, comment = read_metadata(path)
414
415     if os.path.isfile(path):
416         size = os.path.getsize(path)
417         type_ = ST_FILE
418     else:
419         size = 0
420         type_ = ST_USERDIR
421
422     size = min(size, 2 ** 31 - 1)
423     fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore')
424     comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore')
425     return struct.pack('>HHHhIIIII', 1, 0, disk_key, type_, size, protection, days, mins, ticks) + fn + comment
426
427 def process_examine_fh(arg1):
428     logger.debug('ACTION_EXAMINE_FH, arg1: %s', arg1)
429
430     fn = open_file_handles[arg1].f.name
431     path = os.path.realpath(fn)
432     days, mins, ticks = mtime_to_dmt(os.path.getmtime(path))
433     protection, comment = read_metadata(path)
434
435     if os.path.isfile(path):
436         size = os.path.getsize(path)
437         type_ = ST_FILE
438     else:
439         size = 0
440         type_ = ST_USERDIR
441
442     size = min(size, 2 ** 31 - 1)
443     fn = (chr(len(fn)) + fn).encode('latin-1', 'ignore')
444     comment = (chr(len(comment)) + comment).encode('latin-1', 'ignore')
445     return struct.pack('>HHHhIIIII', 1, 0, 666, type_, size, protection, days, mins, ticks) + fn + comment
446
447
448 next_fp = 1
449
450 open_file_handles = {}
451
452 def get_file_ptr():
453     global next_fp
454     fp = next_fp
455     next_fp = 1 if next_fp == 0x7fffffff else next_fp + 1
456     while fp in open_file_handles:
457         fp += 1
458     return fp
459
460 class OpenFileHandle(object):
461     def __init__(self, fp, f, p):
462         self.fp = fp
463         self.f = f
464         self.p = p
465
466 def process_findxxx(mode, key, name):
467     if mode == ACTION_FINDINPUT:
468         logger.debug('ACTION_FINDINPUT, key: %s, name: %s', key, name)
469     elif mode == ACTION_FINDOUTPUT:
470         logger.debug('ACTION_FINDOUTPUT, key: %s, name: %s', key, name)
471     elif mode == ACTION_FINDUPDATE:
472         logger.debug('ACTION_FINDUPDATE, key: %s, name: %s', key, name)
473
474     cp = find_path(key, name)
475     if cp is None:
476         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
477
478     path = '/'.join(cp)
479     if len(cp) == 0 or os.path.isdir(path):
480         return struct.pack('>HH', 0, ERROR_OBJECT_WRONG_TYPE)
481
482     # TODO: Must check if there already exists a non-compatible lock for this path.
483
484     # TODO: This must be handled better. Especially error reporting.
485
486     protection, _ = read_metadata(path)
487     try:
488         if mode == MODE_OLDFILE:
489             f = open(path, 'r+b')
490         elif mode == MODE_READWRITE:
491             f = open(path, 'r+b')
492             if protection & 16:
493                 protection = protection & 0b11101111
494                 write_metadata(path, protection=protection)
495         elif mode == MODE_NEWFILE:
496             if protection & 0x1:
497                 return struct.pack('>HH', 0, ERROR_DELETE_PROTECTED)
498             elif protection & 0x4:
499                 return struct.pack('>HH', 0, ERROR_WRITE_PROTECTED)
500             f = open(path, 'w+b')
501             if protection & 16:
502                 protection = protection & 0b11101111
503                 write_metadata(path, protection=protection)
504     except IOError:
505         if mode == MODE_READWRITE:
506             try:
507                 f = open(path, 'w+b')
508                 if protection & 16:
509                     protection = protection & 0b11101111
510                     write_metadata(path, protection=protection)
511             except IOError:
512                 return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
513         else:
514             return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
515
516     fp = get_file_ptr()
517     ofh = OpenFileHandle(fp, f, protection)
518     open_file_handles[fp] = ofh
519
520     return struct.pack('>HHI', 1, 0, fp)
521
522 def process_read(arg1, address, length):
523     logger.debug('ACTION_READ, arg1: %s, address: %s, length: %s', arg1, address, length)
524     protection = open_file_handles[arg1].p
525     if protection & 0x8:
526         return struct.pack('>HH', 0, ERROR_READ_PROTECTED)
527     f = open_file_handles[arg1].f
528     data = f.read(length)
529     if len(data) != 0:
530         write_mem(address, data)
531     return struct.pack('>HHI', 1, 0, len(data))
532
533 def process_write(arg1, address, length):
534     logger.debug('ACTION_WRITE, arg1: %s, address: %s, length: %s', arg1, address, length)
535     protection = open_file_handles[arg1].p
536     if protection & 0x4:
537         return struct.pack('>HH', 0, ERROR_WRITE_PROTECTED)
538     data = read_mem(address, length)
539     f = open_file_handles[arg1].f
540     f.seek(0, 1)
541     try:
542         f.write(data)
543     except IOError:
544         return struct.pack('>HH', 0, ERROR_DISK_FULL)
545     return struct.pack('>HHI', 1, 0, length)
546
547 def process_seek(arg1, new_pos, mode):
548     logger.debug('ACTION_SEEK, arg1: %s, new_pos: %s, mode: %s', arg1, new_pos, mode)
549
550     f = open_file_handles[arg1].f
551     old_pos = f.tell()
552
553     from_what = 0
554     if mode == OFFSET_CURRENT:
555         from_what = 1
556     elif mode == OFFSET_END:
557         from_what = 2
558
559     f.seek(new_pos, from_what)
560
561     return struct.pack('>HHi', 1, 0, old_pos)
562
563 def process_end(arg1):
564     logger.debug('ACTION_END, arg1: %s', arg1)
565
566     if arg1 in open_file_handles:
567         f = open_file_handles.pop(arg1).f
568         f.close()
569
570     return struct.pack('>HH', 1, 0)
571
572 def process_delete_object(key, name):
573     logger.debug('ACTION_DELETE_OBJECT, key: %s, name: %s', key, name)
574
575     cp = find_path(key, name)
576     if cp is None or len(cp) == 0:
577         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
578
579     path = '/'.join(cp)
580     is_dir = os.path.isdir(path)
581
582     protection, _ = read_metadata(path)
583     if protection & 0x1:
584         return struct.pack('>HH', 0, ERROR_DELETE_PROTECTED)
585
586     try:
587         if is_dir:
588             os.rmdir(path)
589         else:
590             os.remove(path)
591         if os.path.isfile(path + METAFILE_EXTENSION):
592             os.remove(path + METAFILE_EXTENSION)
593     except:
594         if is_dir:
595             return struct.pack('>HH', 0, ERROR_DIRECTORY_NOT_EMPTY)
596         else:
597             return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
598
599     return struct.pack('>HH', 1, 0)
600
601 def process_rename_object(key, name, target_dir, new_name):
602     logger.debug('ACTION_RENAME_OBJECT, key: %s, name: %s, target_dir: %s, new_name: %s', key, name, target_dir, new_name)
603
604     cp1 = find_path(key, name)
605     if cp1 is None or len(cp1) == 0:
606         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
607
608     from_path = '/'.join(cp1)
609     if not os.path.exists(from_path):
610         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
611
612     cp2 = find_path(target_dir, new_name)
613     if cp2 is None or len(cp2) == 0:
614         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
615
616     to_path = '/'.join(cp2)
617
618     if from_path == to_path:
619         return struct.pack('>HH', 1, 0)
620
621     if os.path.exists(to_path):
622         return struct.pack('>HH', 0, ERROR_OBJECT_EXISTS)
623
624     try:
625         os.rename(from_path, to_path)
626         if os.path.isfile(from_path + METAFILE_EXTENSION):
627             os.rename(from_path + METAFILE_EXTENSION, to_path + METAFILE_EXTENSION)
628     except:
629         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
630
631     return struct.pack('>HH', 1, 0)
632
633 def process_create_dir(key, name):
634     logger.debug('ACTION_CREATE_DIR, key: %s, name: %s', key, name)
635
636     cp = find_path(key, name)
637     if cp is None or len(cp) == 0:
638         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
639
640     try:
641         path = '/'.join(cp)
642         os.makedirs(path)
643     except:
644         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
645
646     key = get_key()
647     locks[key] = ObjectLock(key, SHARED_LOCK, cp)
648     return struct.pack('>HHI', 1, 0, key)
649
650 def process_set_protect(key, name, mask):
651     logger.debug('ACTION_SET_PROTECT, key: %s, name: %s, mask: %s', key, name, mask)
652
653     cp = find_path(key, name)
654     if cp is None or len(cp) == 0:
655         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
656
657     path = '/'.join(cp)
658     if write_metadata(path, protection=mask):
659         return struct.pack('>HH', 1, 0)
660     else:
661         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
662
663 def process_set_comment(key, name, comment):
664     logger.debug('ACTION_SET_COMMENT, key: %s, name: %s, comment: %s', key, name, comment)
665
666     if len(comment) > 79:
667         return struct.pack('>HH', 0, ERROR_COMMENT_TOO_BIG)
668
669     cp = find_path(key, name)
670     if cp is None or len(cp) == 0:
671         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
672
673     path = '/'.join(cp)
674     if write_metadata(path, comment=comment):
675         return struct.pack('>HH', 1, 0)
676     else:
677         return struct.pack('>HH', 0, ERROR_OBJECT_NOT_FOUND)
678
679 def process_same_lock(key1, key2):
680     logger.debug('ACTION_SAME_LOCK, key1: %s key2: %s', key1, key2)
681
682     if not (key1 in locks and key2 in locks):
683         return struct.pack('>HH', 0, LOCK_DIFFERENT)
684     elif locks[key1].path == locks[key2].path:
685         return struct.pack('>HH', 1, LOCK_SAME)
686     else:
687         return struct.pack('>HH', 0, LOCK_SAME_VOLUME)
688
689 def process_request(req):
690     #logger.debug('len(req): %s, req: %s', len(req), list(req))
691
692     (rtype,) = struct.unpack('>H', req[:2])
693
694     if rtype == ACTION_LOCATE_OBJECT:
695         key, mode, nlen = struct.unpack('>IHB', req[2:9])
696         name = req[9:9+nlen].decode('latin-1')
697         return process_locate_object(key, mode, name)
698     elif rtype == ACTION_FREE_LOCK:
699         (key,) = struct.unpack('>I', req[2:6])
700         return process_free_lock(key)
701     elif rtype == ACTION_COPY_DIR:
702         (key,) = struct.unpack('>I', req[2:6])
703         return process_copy_dir(key)
704     elif rtype == ACTION_PARENT:
705         (key,) = struct.unpack('>I', req[2:6])
706         return process_parent(key)
707     elif rtype == ACTION_EXAMINE_OBJECT:
708         (key,) = struct.unpack('>I', req[2:6])
709         return process_examine_object(key)
710     elif rtype == ACTION_EXAMINE_NEXT:
711         key, disk_key = struct.unpack('>IH', req[2:8])
712         return process_examine_next(key, disk_key)
713     elif rtype == ACTION_EXAMINE_FH:
714         (arg1,) = struct.unpack('>I', req[2:6])
715         return process_examine_fh(arg1)
716     elif rtype == ACTION_FINDINPUT or rtype == ACTION_FINDOUTPUT or rtype == ACTION_FINDUPDATE:
717         key, nlen = struct.unpack('>IB', req[2:7])
718         name = req[7:7+nlen].decode('latin-1')
719         return process_findxxx(rtype, key, name)
720     elif rtype == ACTION_READ:
721         arg1, address, length = struct.unpack('>III', req[2:14])
722         return process_read(arg1, address, length)
723     elif rtype == ACTION_WRITE:
724         arg1, address, length = struct.unpack('>III', req[2:14])
725         return process_write(arg1, address, length)
726     elif rtype == ACTION_SEEK:
727         arg1, new_pos, mode = struct.unpack('>Iii', req[2:14])
728         return process_seek(arg1, new_pos, mode)
729     elif rtype == ACTION_END:
730         (arg1,) = struct.unpack('>I', req[2:6])
731         return process_end(arg1)
732     elif rtype == ACTION_DELETE_OBJECT:
733         key, nlen = struct.unpack('>IB', req[2:7])
734         name = req[7:7+nlen].decode('latin-1')
735         return process_delete_object(key, name)
736     elif rtype == ACTION_RENAME_OBJECT:
737         key, target_dir, nlen, nnlen = struct.unpack('>IIBB', req[2:12])
738         name = req[12:12+nlen].decode('latin-1')
739         new_name = req[12+nlen:12+nlen+nnlen].decode('latin-1')
740         return process_rename_object(key, name, target_dir, new_name)
741     elif rtype == ACTION_CREATE_DIR:
742         key, nlen = struct.unpack('>IB', req[2:7])
743         name = req[7:7+nlen].decode('latin-1')
744         return process_create_dir(key, name)
745     elif rtype == ACTION_SET_PROTECT:
746         key, mask, nlen = struct.unpack('>IIB', req[2:11])
747         name = req[11:11+nlen].decode('latin-1')
748         return process_set_protect(key, name, mask)
749     elif rtype == ACTION_SET_COMMENT:
750         key, nlen, clen = struct.unpack('>IBB', req[2:8])
751         name = req[8:8+nlen].decode('latin-1')
752         comment = req[8+nlen:8+nlen+clen].decode('latin-1')
753         return process_set_comment(key, name, comment)
754     elif rtype == ACTION_SAME_LOCK:
755         key1, key2 = struct.unpack('>II', req[2:10])
756         return process_same_lock(key1, key2)
757     elif rtype == ACTION_UNSUPPORTED:
758         (dp_Type,) = struct.unpack('>H', req[2:4])
759         logger.warning('Unsupported action %d (Amiga/a314fs)', dp_Type)
760         return struct.pack('>HH', 0, ERROR_ACTION_NOT_KNOWN)
761     else:
762         logger.warning('Unsupported action %d (a314d/a314fs)', rtype)
763         return struct.pack('>HH', 0, ERROR_ACTION_NOT_KNOWN)
764
765 done = False
766
767 try:
768     idx = sys.argv.index('-ondemand')
769 except ValueError:
770     idx = -1
771
772 if idx != -1:
773     fd = int(sys.argv[idx + 1])
774     drv = socket.socket(fileno=fd)
775 else:
776     drv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
777     drv.connect(('localhost', 7110))
778
779     send_register_req(b'a314fs')
780     stream_id, ptype, payload = wait_for_msg()
781     if payload[0] != 1:
782         logger.error('Unable to register service a314fs, shutting down')
783         drv.close()
784         done = True
785
786 if not done:
787     os.chdir(SHARED_DIRECTORY)
788     logger.info('a314fs is running, shared directory: %s', SHARED_DIRECTORY)
789
790 while not done:
791     stream_id, ptype, payload = wait_for_msg()
792
793     if ptype == MSG_CONNECT:
794         if payload == b'a314fs':
795             if current_stream_id is not None:
796                 send_reset(current_stream_id)
797             current_stream_id = stream_id
798             send_connect_response(stream_id, 0)
799         else:
800             send_connect_response(stream_id, 3)
801     elif ptype == MSG_DATA:
802         address, length = struct.unpack('>II', payload)
803         #logger.debug('address: %s, length: %s', address, length)
804         req = read_mem(address + 2, length - 2)
805         res = process_request(req)
806         write_mem(address + 2, res)
807         #write_mem(address, b'\xff\xff')
808         send_data(stream_id, b'\xff\xff')
809     elif ptype == MSG_EOS:
810         if stream_id == current_stream_id:
811             logger.debug('Got EOS, stream closed')
812             send_eos(stream_id)
813             current_stream_id = None
814     elif ptype == MSG_RESET:
815         if stream_id == current_stream_id:
816             logger.debug('Got RESET, stream closed')
817             current_stream_id = None