From 6dcba5568fad868b9b187f78cbf5ff1a4dbd0fda Mon Sep 17 00:00:00 2001 From: Andrew Borodin Date: Sun, 3 Jan 2016 10:47:23 +0300 Subject: [PATCH] Ticket #3527: uc1541 extfs: update version. The current version is 2.8: besides of some tweaks and fixes, support for compressed disk images was introduced. Signed-off-by: Andrew Borodin --- src/vfs/extfs/helpers/uc1541.in | 211 +++++++++++++++----------------- 1 file changed, 96 insertions(+), 115 deletions(-) diff --git a/src/vfs/extfs/helpers/uc1541.in b/src/vfs/extfs/helpers/uc1541.in index 42d59d4e3..dd7b7c410 100644 --- a/src/vfs/extfs/helpers/uc1541.in +++ b/src/vfs/extfs/helpers/uc1541.in @@ -2,83 +2,18 @@ """ UC1541 Virtual filesystem -This extfs provides an access to disk image files for the Commodore -VIC20/C64/C128. It requires the utility c1541 that comes bundled with Vice, -the emulator for the VIC20, C64, C128 and other computers made by Commodore. - -Remarks -------- - -Due to different way of representing file entries on regular D64 disk images, -there could be issues with filenames that are transferred from/to the image. -Following rules was applied to represent a single file entry: - -1. An extension is attached to the end of a filename depending on a file type. - Possible extensions are: prg, del, seq, usr and rel. -2. Every non-ASCII character (which could be some of characters specific to - PET-ASCII, or be a control character) will be replaced by dot (.), since - c1541 program will list them that way. -3. Every slash character (/) will be replaced by pipe character (|). -4. Leading space will be replaced by tilda (~). - -While copying from D64 image to filesystem, filenames will be stored as they -are seen on a listing. - -While copying from filesystem to D64 image, filename conversion will be done: -1. Every $ and * characters will be replaced by question mark (?) -2. Every pipe (|) and backslash (\) characters will be replaced by slash (/) -3. Every tilda (~) will be replaced by a space -4. 'prg' extension will be truncated - -Representation of a directory can be sometimes confusing - in case when one -copied file without extension it stays there in such form, till next access -(after flushing VFS). Also file sizes are not accurate, since D64 directory -entries have sizes stored as 256 bytes blocks. - -Configuration -------------- - -Here are specific for this script variable, which while set, can influence -script behaviour: - -UC1541_DEBUG - if set, uc1541 will produce log in /tmp/uc1541.log file - -UC1541_VERBOSE - of set, script will be more verbose, i.e. error messages form -c1541 program will be passed to Midnight Commander, so that user will be aware -of error cause if any. - -UC1541_HIDE_DEL - if set, no DEL entries will be shown - -Changelog: - 2.5 Fixed bug with filenames started with a '-' sign. - 2.4 Fixed endless loop bug for reading directory in Python implemented - directory reader. - 2.3 Re added and missing method _correct_fname used for writing files - into d64 image. - 2.2 Fixed bug(?) with unusual sector end (marked as sector 0, not 255), - causing endless directory reading on random locations. - 2.1 Fixed bug with filenames containing slash. - 2.0 Added reading raw D64 image, and mapping for jokers. Now it is - possible to read files with PET-ASCII/control sequences in filenames. - Working with d64 images only. Added workaround for space at the - beginning of the filename. - 1.2 Added configuration env variables: UC1541_VERBOSE and UC1541_HIDE_DEL. - First one, if set to any value, will cause that error messages from - c1541 program will be redirected as a failure messages visible in MC. - The other variable, when set to any value, cause "del" entries to be - not shown in the lister. - 1.1 Added protect bits, added failsafe for argparse module - 1.0 Initial release - Author: Roman 'gryf' Dobosz -Date: 2012-10-15 -Version: 2.5 +Date: 2014-01-04 +Version: 2.8 Licence: BSD +source: https://bitbucket.org/gryf/uc1541 +mirror: https://github.com/gryf/uc1541 """ import sys import re import os +import gzip from subprocess import Popen, PIPE if os.getenv('UC1541_DEBUG'): @@ -94,7 +29,7 @@ if os.getenv('UC1541_DEBUG'): else: class LOG(object): """ - Dummy logger object. does nothing. + Dummy logger object. Does nothing. """ @classmethod def debug(*args, **kwargs): @@ -151,16 +86,31 @@ class D64(object): Init """ LOG.debug('image: %s', dimage) - dimage = open(dimage, 'rb') - self.raw = dimage.read() - dimage.close() - + self.raw = None self.current_sector_data = None self.next_sector = 0 self.next_track = None self._dir_contents = [] self._already_done = [] + self._get_raw(dimage) + + def _get_raw(self, dimage): + """Try to get contents of the D64 image either it's gzip compressed or + not.""" + fobj = gzip.open(dimage) + # Although the common approach with gzipped files is to check the + # magic number, in this case there is no guarantee that first track + # does not contain exactly the same byte sequence as the magic number. + # So the only way left is to actually try to uncompress the file. + try: + self.raw = fobj.read() + except IOError: + fobj.close() + fobj = open(dimage) + self.raw = fobj.read() + fobj.close() + def _map_filename(self, string): """ Transcode filename to ASCII compatible. Replace not supported @@ -208,6 +158,11 @@ class D64(object): self.current_sector_data = self.raw[offset:offset + 256] + # Guard for reading data out of bound - that happened for discs which + # store only raw data, even on 18 track + if not self.current_sector_data: + return False + self.next_track = ord(self.current_sector_data[0]) self.next_sector = ord(self.current_sector_data[1]) @@ -260,7 +215,7 @@ class D64(object): Traverse through sectors and store entries in _dir_contents """ sector = self.current_sector_data - for x in range(8): + for dummy in range(8): entry = sector[:32] ftype = ord(entry[2]) @@ -320,6 +275,11 @@ class Uc1541(object): LOG.info("List contents of %s", self.arch) directory = self._get_dir() + # If there is an error reading directory, show the reason to the user + if self.out.startswith("Error"): + sys.stderr.write(self.out.split("\n")[0] + "\n") + return 2 + for entry in directory: sys.stdout.write("%(perms)s 1 %(uid)-8d %(gid)-8d %(size)8d " "Jan 01 1980 %(display_name)s\n" % entry) @@ -373,6 +333,16 @@ class Uc1541(object): return 0 + def mkdir(self, dirname): + """Not supported""" + self.err = "D64 format doesn't support directories" + return self._show_error() + + def run(self, fname): + """Not supported""" + self.err = "Not supported, unless you are using MC on real C64 ;)" + return self._show_error() + def _correct_fname(self, fname): """ Return filename with mapped characters, without .prg extension. @@ -439,7 +409,7 @@ class Uc1541(object): if '/' in display_name: display_name = display_name.replace('/', '|') - # workaround for space and dash at the beginning of the + # workaround for space and dash at the beggining of the # filename char_map = {' ': '~', '-': '_'} @@ -466,9 +436,9 @@ class Uc1541(object): Pass out error output from c1541 execution """ if self._verbose: - sys.exit(self.err) + return self.err else: - sys.exit(1) + return 1 def _call_command(self, cmd, src=None, dst=None): """ @@ -479,27 +449,26 @@ class Uc1541(object): dir/list """ command = ['c1541', '-attach', self.arch, '-%s' % cmd] - if src and dst: + if src: command.append(src) + if dst: command.append(dst) - elif src or dst: - command.append(src and src or dst) self.out, self.err = Popen(command, stdout=PIPE, stderr=PIPE).communicate() return not self.err -CALL_MAP = {'list': lambda a: Uc1541(a.ARCH).list(), - 'copyin': lambda a: Uc1541(a.ARCH).copyin(a.SRC, a.DST), - 'copyout': lambda a: Uc1541(a.ARCH).copyout(a.SRC, a.DST), - 'rm': lambda a: Uc1541(a.ARCH).rm(a.DST)} +CALL_MAP = {'list': lambda a: Uc1541(a.arch).list(), + 'copyin': lambda a: Uc1541(a.arch).copyin(a.src, a.dst), + 'copyout': lambda a: Uc1541(a.arch).copyout(a.src, a.dst), + 'mkdir': lambda a: Uc1541(a.arch).mkdir(a.dst), + 'rm': lambda a: Uc1541(a.arch).rm(a.dst), + 'run': lambda a: Uc1541(a.arch).run(a.dst)} def parse_args(): - """ - Use ArgumentParser to check for script arguments and execute. - """ + """Use ArgumentParser to check for script arguments and execute.""" parser = ArgumentParser() subparsers = parser.add_subparsers(help='supported commands') parser_list = subparsers.add_parser('list', help="List contents of D64 " @@ -509,67 +478,79 @@ def parse_args(): parser_copyout = subparsers.add_parser('copyout', help="Copy file out of " "D64 image") parser_rm = subparsers.add_parser('rm', help="Delete file from D64 image") + parser_mkdir = subparsers.add_parser('mkdir', help="Create directory in " + "archive") + parser_run = subparsers.add_parser('run', help="Execute archived file") - parser_list.add_argument('ARCH', help="D64 Image filename") + parser_list.add_argument('arch', help="D64 Image filename") parser_list.set_defaults(func=CALL_MAP['list']) - parser_copyin.add_argument('ARCH', help="D64 Image filename") - parser_copyin.add_argument('SRC', help="Source filename") - parser_copyin.add_argument('DST', help="Destination filename (to be " + parser_copyin.add_argument('arch', help="D64 Image filename") + parser_copyin.add_argument('src', help="Source filename") + parser_copyin.add_argument('dst', help="Destination filename (to be " "written into D64 image)") parser_copyin.set_defaults(func=CALL_MAP['copyin']) - parser_copyout.add_argument('ARCH', help="D64 Image filename") - parser_copyout.add_argument('SRC', help="Source filename (to be read from" + parser_copyout.add_argument('arch', help="D64 Image filename") + parser_copyout.add_argument('src', help="Source filename (to be read from" " D64 image") - parser_copyout.add_argument('DST', help="Destination filename") + parser_copyout.add_argument('dst', help="Destination filename") parser_copyout.set_defaults(func=CALL_MAP['copyout']) - parser_rm.add_argument('ARCH', help="D64 Image filename") - parser_rm.add_argument('DST', help="File inside D64 image to be deleted") + parser_rm.add_argument('arch', help="D64 Image filename") + parser_rm.add_argument('dst', help="File inside D64 image to be deleted") parser_rm.set_defaults(func=CALL_MAP['rm']) + parser_mkdir.add_argument('arch', help="archive filename") + parser_mkdir.add_argument('dst', help="Directory name inside archive to " + "be created") + parser_mkdir.set_defaults(func=CALL_MAP['mkdir']) + + parser_run.add_argument('arch', help="archive filename") + parser_run.add_argument('dst', help="File to be executed") + parser_run.set_defaults(func=CALL_MAP['run']) + args = parser.parse_args() return args.func(args) def no_parse(): - """ - Failsafe argument "parsing". Note, that it blindly takes positional + """Failsafe argument "parsing". Note, that it blindly takes positional arguments without checking them. In case of wrong arguments it will - silently exit - """ + silently exit""" try: - if sys.argv[1] not in ('list', 'copyin', 'copyout', 'rm'): + if sys.argv[1] not in ('list', 'copyin', 'copyout', 'rm', 'mkdir', + "run"): sys.exit(2) except IndexError: sys.exit(2) class Arg(object): - DST = None - SRC = None - ARCH = None + """Mimic argparse object""" + dst = None + src = None + arch = None arg = Arg() try: - arg.ARCH = sys.argv[2] + arg.arch = sys.argv[2] if sys.argv[1] in ('copyin', 'copyout'): - arg.SRC = sys.argv[3] - arg.DST = sys.argv[4] - elif sys.argv[1] == 'rm': - arg.DST = sys.argv[3] + arg.src = sys.argv[3] + arg.dst = sys.argv[4] + elif sys.argv[1] in ('rm', 'run', 'mkdir'): + arg.dst = sys.argv[3] except IndexError: sys.exit(2) - CALL_MAP[sys.argv[1]](arg) + return CALL_MAP[sys.argv[1]](arg) if __name__ == "__main__": LOG.debug("Script params: %s", str(sys.argv)) try: from argparse import ArgumentParser - parse_func = parse_args + PARSE_FUNC = parse_args except ImportError: - parse_func = no_parse + PARSE_FUNC = no_parse - parse_func() + sys.exit(PARSE_FUNC())